pax_global_header00006660000000000000000000000064143633100100014501gustar00rootroot0000000000000052 comment=9057d3466e1ab26b84ab67c91ac212bee8748bce mdnsd-0.12/000077500000000000000000000000001436331001000125305ustar00rootroot00000000000000mdnsd-0.12/.github/000077500000000000000000000000001436331001000140705ustar00rootroot00000000000000mdnsd-0.12/.github/CODE-OF-CONDUCT.md000066400000000000000000000121531436331001000165250ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at troglobit+mdnsd@gmail.com. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. mdnsd-0.12/.github/CONTRIBUTING.md000066400000000000000000000056671436331001000163370ustar00rootroot00000000000000Contributing to mdnsd ===================== We welcome any and all help in the form of bug reports, fixes, patches for new features, *preferably as GitHub pull requests*. Other methods are of course also possible: emailing the maintainer a patch or even a raw file, or simply emailing a feature request or an alert of a problem. However, email questions/requests/alerts always risk memory exhaustion on the part of the maintainer(s). If you are unsure of what to do, or how to implement an idea or bug fix, open an issue with the title `"[RFC: Unsure if this is a bug ... ?"`, or similar, so we can discuss it. Talking about the code first is the best way to get started before submitting a pull request. Either way, when sending an email, patch, or pull request, start by stating the version the change is made against, what it does, and most importanyl -- why. Please take care to ensure you follow the project *coding style* and the commit message format. If you follow these recommendations you help the maintainer(s) and make it easier for them to include your code. Coding Style ------------ > **Tip:** Always submit code that follows the style of surrounding code! First of all, lines are allowed to be longer than 72 characters these days. In fact, there exist no enforced maximum, but keeping it around 100 chars is OK. The coding style itself is otherwise strictly Linux [KNF][]. Commit Messages --------------- Commit messages exist to track *why* a change was made. Try to be as clear and concise as possible in your commit messages, and always, be proud of your work and set up a proper GIT identity for your commits: git config --global user.name "Jane Doe" git config --global user.email jane.doe@example.com Example commit message from the [Pro Git][gitbook] online book, notice how `git commit -s` is used to automatically add a `Signed-off-by`: Brief, but clear and concise summary of changes More detailed explanatory text, if necessary. Wrap it to about 72 characters or so. In some contexts, the first line is treated as the subject of an email and the rest of the text as the body. The blank line separating the ummary from the body is critical (unless you omit the body entirely); tools like rebase can get confused if you run the two together. Further paragraphs come after blank lines. - Bullet points are okay, too - Typically a hyphen or asterisk is used for the bullet, preceded by a single space, with blank lines in between, but conventions vary here Signed-off-by: Jane Doe Code of Conduct --------------- It is expected of everyone engaging in the project to, in the words of Bill & Ted; [be excellent to each other][conduct]. [KNF]: https://en.wikipedia.org/wiki/Kernel_Normal_Form [gitbook]: https://git-scm.com/book/ch5-2.html [conduct]: https://github.com/troglobit/mdnsd/blob/master/.github/CODE-OF-CONDUCT.md mdnsd-0.12/.github/FUNDING.yml000066400000000000000000000001031436331001000156770ustar00rootroot00000000000000# These are supported funding model platforms github: [troglobit] mdnsd-0.12/.github/workflows/000077500000000000000000000000001436331001000161255ustar00rootroot00000000000000mdnsd-0.12/.github/workflows/build.yml000066400000000000000000000035511436331001000177530ustar00rootroot00000000000000name: Bob the Builder # Run on all branches, including all pull requests, except the 'dev' # branch since that's where we run Coverity Scan (limited tokens/day) on: push: branches: - '**' - '!dev' pull_request: branches: - '**' jobs: build: # Verify we can build on latest Ubuntu with both gcc and clang name: ${{ matrix.compiler }} runs-on: ubuntu-latest strategy: matrix: compiler: [gcc, clang] fail-fast: false env: MAKEFLAGS: -j3 CC: ${{ matrix.compiler }} steps: - name: Install dependencies run: | sudo apt-get -y update sudo apt-get -y install tree doxygen libcmocka-dev - uses: actions/checkout@v3 - name: Configure run: | ./autogen.sh ./configure --prefix= --disable-silent-rules --enable-tests - name: Build run: | make - name: Install to ~/tmp and Inspect run: | DESTDIR=~/tmp make install-strip tree ~/tmp LD_LIBRARY_PATH=~/tmp/lib ldd ~/tmp/sbin/mdnsd size ~/tmp/sbin/mdnsd size ~/tmp/lib/libmdnsd.so* LD_LIBRARY_PATH=~/tmp/lib ~/tmp/sbin/mdnsd -h - name: Test run: | make check || (cat test/test-suite.log; false) - name: Upload Test Results uses: actions/upload-artifact@v3 with: name: mdnsd-test-${{ matrix.compiler }} path: test/* distcheck: runs-on: ubuntu-latest env: MAKEFLAGS: -j3 steps: - name: Install dependencies run: | sudo apt-get -y update sudo apt-get -y install tree doxygen libcmocka-dev - uses: actions/checkout@v3 - name: Configure run: | ./autogen.sh ./configure --prefix= - name: Build & Check run: | make distcheck mdnsd-0.12/.github/workflows/coverity.yml000066400000000000000000000053241436331001000205200ustar00rootroot00000000000000name: Coverity Scan on: push: branches: - 'dev' env: PROJECT_NAME: mdnsd CONTACT_EMAIL: troglobit@gmail.com COVERITY_NAME: troglobit-mdnsd COVERITY_PROJ: troglobit%2Fmdnsd jobs: coverity: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Fetch latest Coverity Scan MD5 id: var env: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} run: | wget -q https://scan.coverity.com/download/cxx/linux64 \ --post-data "token=$TOKEN&project=${COVERITY_PROJ}&md5=1" \ -O coverity-latest.tar.gz.md5 export MD5=$(cat coverity-latest.tar.gz.md5) echo "Got MD5 $MD5" echo ::set-output name=md5::${MD5} - uses: actions/cache@v3 id: cache with: path: coverity-latest.tar.gz key: ${{ runner.os }}-coverity-${{ steps.var.outputs.md5 }} restore-keys: | ${{ runner.os }}-coverity-${{ steps.var.outputs.md5 }} ${{ runner.os }}-coverity- ${{ runner.os }}-coverity - name: Download Coverity Scan env: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} run: | if [ ! -f coverity-latest.tar.gz ]; then wget -q https://scan.coverity.com/download/cxx/linux64 \ --post-data "token=$TOKEN&project=${COVERITY_PROJ}" \ -O coverity-latest.tar.gz else echo "Latest Coverity Scan available from cache :-)" md5sum coverity-latest.tar.gz fi mkdir coverity tar xzf coverity-latest.tar.gz --strip 1 -C coverity - name: Install dependencies run: | sudo apt-get -y update sudo apt-get -y install pkg-config libsystemd-dev libcmocka-dev - name: Configure run: | ./autogen.sh ./configure --prefix= --enable-examples --enable-tests - name: Build run: | export PATH=`pwd`/coverity/bin:$PATH cov-build --dir cov-int make - name: Submit results to Coverity Scan env: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} run: | tar czvf ${PROJECT_NAME}.tgz cov-int curl \ --form project=${COVERITY_NAME} \ --form token=$TOKEN \ --form email=${CONTACT_EMAIL} \ --form file=@${PROJECT_NAME}.tgz \ --form version=trunk \ --form description="${PROJECT_NAME} $(git rev-parse HEAD)" \ https://scan.coverity.com/builds?project=${COVERITY_PROJ} - name: Upload build.log uses: actions/upload-artifact@v3 with: name: coverity-build.log path: cov-int/build-log.txt mdnsd-0.12/.github/workflows/release.yml000066400000000000000000000020521436331001000202670ustar00rootroot00000000000000name: Release General on: push: tags: - 'v*' jobs: release: name: Build and upload release tarball if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Installing dependencies ... run: | sudo apt-get -y update sudo apt-get -y install tree doxygen libcmocka-dev - name: Creating Makefiles ... run: | ./autogen.sh ./configure --prefix=/tmp --enable-tests --with-systemd=/tmp/lib/systemd/system - name: Build release ... run: | make release ls -lF ../ mkdir -p artifacts/ mv ../*.tar.* artifacts/ - name: Extract ChangeLog entry ... run: | awk '/-----*/{if (x == 1) exit; x=1;next}x' ChangeLog.md \ |head -n -1 > release.md cat release.md - uses: ncipollo/release-action@v1 with: name: mdnsd ${{ github.ref_name }} bodyFile: "release.md" artifacts: "artifacts/*" mdnsd-0.12/.gitignore000066400000000000000000000003501436331001000145160ustar00rootroot00000000000000*~ *.swp .config .gdb_history .gdbinit .unpacked autom4te.cache/ GPATH GRTAGS GSYMS GTAGS ID Makefile Makefile.in aclocal.m4 ar-lib aux/ compile config.* configure depcomp install-sh libtool ltmain.sh missing stamp-h1 mdnsd.service mdnsd-0.12/API.md000066400000000000000000000026721436331001000134720ustar00rootroot00000000000000Library ------- There are several use-cases for this project, the daemon provides one use of the library, yet many others are possible. Here is a small example how to publish a few records: char hlocal[384]; char nlocal[384]; char hostname[256]; char *path = "/path/to/service/" gethostname(hostname, sizeof(hostname)); sprintf(hlocal, "%s._http._tcp.local.", hostname); sprintf(nlocal, "%s.local.", hostname); /* Announce that we have a _http._tcp service */ r = mdnsd_shared(d, "_services._dns-sd._udp.local.", QTYPE_PTR, 120); mdnsd_set_host(d, r, "_http._tcp.local."); r = mdnsd_shared(d, "_http._tcp.local.", QTYPE_PTR, 120); mdnsd_set_host(d, r, hlocal); r = mdnsd_unique(d, hlocal, QTYPE_SRV, 600, conflict, NULL); mdnsd_set_srv(d, r, 0, 0, port, nlocal); r = mdnsd_unique(d, nlocal, QTYPE_A, 600, conflict, NULL); mdnsd_set_ip(d, r, ip_addr); r = mdnsd_unique(d, nlocal, QTYPE_AAAA, 600, conflict, NULL); mdnsd_set_ipv6(d, r, ip6_addr); r = mdnsd_unique(d, hlocal, QTYPE_TXT, 600, conflict, NULL); h = xht_new(11); if (path && strlen(path)) xht_set(h, "path", path); packet = sd2txt(h, &len); xht_free(h); mdnsd_set_raw(d, r, (char *)packet, len); free(packet); How to read a previously published record: r = mdnsd_get_published(d, "_http._tcp.local."); while (r) { const mdns_answer_t *data; data = mdnsd_record_data(r); if (data) DBG("Found record of type %d", data->type); r = mdnsd_record_next(r); } mdnsd-0.12/ChangeLog.md000066400000000000000000000147371436331001000147150ustar00rootroot00000000000000Change Log ========== All relevant changes to the project are documented in this file. [v0.12][] - 2023-01-22 ---------------------- IPv6 support, including a lot of fixes and cleanup again from the team at [devolo AG](https://www.devolo.com). ### Changes - libmdnsd, mquery, and mdnsd: add support for IPv6 AAAA records, including display in mquery, by Florian Zschocke, devolo AG - mdnsd: add `-H hostname` support, for testing purposes mainly - mquery: add `-l debug` support - mquery: Display every answer we get when in `mdnsd-scan` mode - Initial support for test framework, including unit testing w/ cmocka - Add support for disabling installation of man pages - Add support for building without `mquery` ### Fixes - Fix #11: port to other UNIX systems, currently tested on the following operating systems, except Linux, should also work on later versions: - FreeBSD 13 - NetBSD 9.1 - OpenBSD 6.8 - DragonFly BSD 5.8 - SunOS solaris 5.11 omnios-r151034 - Fix #49: various typos in log messages - Fix #52: double free, introduced in v0.11 - Fix #55: mDNS conflict check, fixed by Florian Zschocke, devolo AG - Fix #56: parsing of A record from packet must maintain network byte order, found and fixed by Florian Zschocke, devolo AG - Fix #65: libmdnsd: Make SPRIME actually a prime number, found and fixed by Florian Zschocke, devolo AG - Fix #74: fix segfault when mdnsd interface is removed [v0.11][] - 2022-01-09 ---------------------- Multiple interface support, and a lot of fixes again from the team at [devolo AG](https://www.devolo.com). ### Changes - Add support for multiple interfaces. Similar to running one mdnsd v0.10 per interface, sharing the same .conf files, issue #8 - Drop `-a ADDR` command line option, not applicable anymore - Drop `-p` command line option, not needed anymore - The `-i IFACE` option now limits mdnsd to run on one interface - Add support for query type ANY, by Peter Fleer, issue #34 - The libmdnsd function `mdnsd_step()` is now always non-blocking by employing `MSG_DONTWAIT` for its `sendto()` and `recvfrom()` ops - Create PID file when starting up, and touch on .conf reload. This is used by process supervisors like Finit to acknowledge that the process (mdnsd) is done and ready to serve requests again - Updated mquery tool to behave a bit more like mdns-scan(1), except with more command line options. mquery still only runs on one interface, unlike mdnsd itself ### Fixes - Fix #33: multiple code cleanups and minor fixes by Wolfgang Rösler - Fix #35: validate port number in .conf file - Fix #38: possible NULL pointer dereference and memory leaks when reading .conf files, by Wolfgang Rösler - Fix #39: multiple fixes by Peter Fleer - Fix conflict detection - If a conflict is detected, append `-index` suffix to hostname to uniqify the name -- this happens in multi-deploy scenarios - When reloading .conf files, delete previously published records - Drop own, looped back, multicast packets -- only if we detect it as our own published records - Send publish records only if probes have gone unanswered - Fix #43: `_ldecomp()` breaking on long name offsets, by Chris Beaumont [v0.10][] - 2020-05-06 ---------------------- Bug fix release, thanks to [devolo AG](https://www.devolo.com). ### Changes - Update man page and add `mdnsd.service.5` to document how to write service record files. ### Fixes - Memory leak fixes courtesy of Peter Fleer and Wolfram Rösler - Append text field to the TXT record instead of the A record, in the case of a PTR type, the name must also match. Found and fixed by Peter Fleer - Fix potential endless loop when decoding message labels, found and fixed by Wolfram Rösler - Skip message processing when the packet parser failed, found and fixed by Wolfram Rösler - Misc. fuzzer fixes, found and fixed by Wolfram Rösler [v0.9][] - 2020-04-02 --------------------- Bug fix and license clarification release. ### Changes - Update Debian packaging, split into several packages - Minor updates to README - Add manual pages mdnsd(8) and mquery(1) - Clarify placeholders in BSD-3 license, spotted by Thomas Bong ### Fixes - Fix #20: segfault that may occur when a new node is allocated, by Colin MacKenzie IV - Fix #22: check for both name *and* type in `mdnsd_find()`, otherwise any previous name matching may be considered an existing node, found and fixed by Thomas Bong - Fix #23: possible NULL pointer dereference when comparing strings [v0.8][] - 2018-08-23 --------------------- Huge thanks to Jeremie Miller for the original mDNS implementation, to Stefan Profanter for fixing the code from the early days, and also to Thom Nichols for careful testing and nudging me to finalize the work and get a proper release out there. Apologies for the terse change log, there are a lot of changes to Jer's original, in a sense this is a brand new project. This is a pre-release of the upcoming v1.0 with some important to remember limitations: - no IPv6, - only handles one interface (no multi-homing), and - only one A record can be announced per service ### Changes - Added support for systemd unit file - Added example SSH service record in `/etc/mdns.d/ssh.service` - Added support for building Debian/Ubuntu `.deb` packages - Renamed example mhttp appliation to mdnsd - Added support for running as a proper UNIX daemon - Added support for logging to syslog - Added proper option parsing - Added configuration file support, or rather DNS record file support - Added GNU configure & build system to ease building and portability - Added support for mDNS-SD service discovery/enumeration, i.e., the `mdns-scan` tool finds and reports services mdnsd announces - Added support for announcing multiple service records ### Fixes - Build fixes for recent versions of clang and gcc - Build fixes for Alpine Linux and other musl libc based systems - Default IP TTL value for multicast frames (224.0.0.251) is now 1 - Fixed endian problems and other issues with `mdns_set_ip()` - Fixed triggering of unicast reply to mDNS query due to missing `ntohs()` - Fixed service record TTLs; 120 and 4500 are RFC recommended values - Fixed memory leaks [UNRELEASED]: https://github.com/troglobit/mdnsd/compare/v0.12...HEAD [v0.12]: https://github.com/troglobit/mdnsd/compare/v0.11...v0.12 [v0.11]: https://github.com/troglobit/mdnsd/compare/v0.10...v0.11 [v0.10]: https://github.com/troglobit/mdnsd/compare/v0.9...v0.10 [v0.9]: https://github.com/troglobit/mdnsd/compare/v0.8...v0.9 [v0.8]: https://github.com/troglobit/mdnsd/compare/v0.7G...v0.8 mdnsd-0.12/LICENSE000066400000000000000000000031021436331001000135310ustar00rootroot00000000000000Copyright (c) 2003 Jeremie Miller Copyright (c) 2016-2022 Joachim Wiberg All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. mdnsd-0.12/Makefile.am000066400000000000000000000034571436331001000145750ustar00rootroot00000000000000ACLOCAL_AMFLAGS = -I m4 nobase_include_HEADERS = libmdnsd/mdnsd.h libmdnsd/1035.h libmdnsd/sdtxt.h libmdnsd/xht.h SUBDIRS = examples libmdnsd src doc_DATA = README.md ChangeLog.md LICENSE EXTRA_DIST = README.md ChangeLog.md LICENSE if HAVE_SYSTEMD systemd_DATA = mdnsd.service endif if DOC SUBDIRS += man endif if ENABLE_TESTS SUBDIRS += test endif # # Check if tagged in git # release-hook: @if [ ! `git tag -l v$(PACKAGE_VERSION) | grep $(PACKAGE_VERSION)` ]; then \ echo; \ printf "\e[1m\e[41mCannot find release tag $(PACKAGE_VERSION)\e[0m\n"; \ printf "\e[1m\e[5mDo release anyway?\e[0m "; read yorn; \ if [ "$$yorn" != "y" -a "$$yorn" != "Y" ]; then \ printf "OK, aborting release.\n"; \ exit 1; \ fi; \ echo; \ else \ echo; \ printf "\e[1m\e[42mFound GIT release tag $(PACKAGE_VERSION)\e[0m\n"; \ printf "\e[1m\e[44m>>Remember to push tags!\e[0m\n"; \ echo; \ fi # # Target to run when building a release # release: release-hook distcheck @for file in $(DIST_ARCHIVES); do \ md5sum $$file > ../$$file.md5; \ sha256sum $$file > ../$$file.sha256; \ done @mv $(DIST_ARCHIVES) ../ @echo @echo "Resulting release files =======================================================================" @for file in $(DIST_ARCHIVES); do \ printf "%-30s Distribution tarball\n" $$file; \ printf "%-30s " $$file.md5; cat ../$$file.md5 | cut -f1 -d' '; \ printf "%-30s " $$file.sha256; cat ../$$file.sha256 | cut -f1 -d' '; \ done # Workaround for systemd unit file duing distcheck DISTCHECK_CONFIGURE_FLAGS = --with-systemd=$$dc_install_base/$(systemd) --disable-silent-rules --enable-tests DISTCLEANFILES = lib/.libs/* mdnsd-0.12/README.md000066400000000000000000000130531436331001000140110ustar00rootroot00000000000000mdnsd - embeddable Multicast DNS Daemon ======================================= [![License svg][]][License] [![Repology svg][]][Repology] [![GitHub svg][]][GitHub] [![Coverity svg][]][Coverity] - [About](#about) - [Usage](#usage) - [Service Records](#service-records) - [Build & Install](#build--install) - [Origin & References](#origin--references) About ----- [Jeremie Miller's][jeremie] original mDNS/mDNS-SD library daemon. > Download a [versioned relased tarball][releases] (not a GitHub zip) to > unlock a fully supported version. Hardcore devs. can proceed to clone > the GIT repository, see below for help. Usage ----- mdnsd by default reads service definitions from `/etc/mdns.d/*`, but a different path can be given, which may be a directory or a single file. Usage: mdnsd [-hnpsv] [-i IFACE] [-l LEVEL] [-t TTL] [PATH] -h This help text -i IFACE Announce services only on this interface, default: all -l LEVEL Set log level: none, err, notice (default), info, debug -n Run in foreground, do not detach from controlling terminal -s Use syslog even if running in foreground -t TTL Set TTL of mDNS packets, default: 1 (link-local only) -v Show program version Bug report address: https://github.com/troglobit/mdnsd/issues By default mdnsd daemonizes, detaches from the controlling terminal and continues running in the background, logging errors (or debug messages if enabled) to the systmem log. There is no output to be expected. On GNU/Linux, use `mdns-scan`, the bundled `mquery` tool, or Wireshark to verify your setup. Other operating systems have their own set of tools for mDNS-SD and mdnsd may not even have a place there. mdnsd runs on all multicast enabled system interfaces. It can be limted to run on only one using the `-i IFACE` command line option. Starting mdnsd early in the boot process, when the interface may not yet have acquired an IP address, or the interface itself may not even exist yet, mdnsd handles this by periodically probing for interface changes. See the file [API.md][] for pointers on how to use the mDNS library. ### Service Records This section provides a couple of service record examples. The syntax of the files is fairly free form. Optional directives: `name`, `txt`, `target`, and `cname`. > **Note:** you need at least one service record for `mdnsd` to respond > to queries from, e.g., `mdns-scan`. _FTP service example:_ # /etc/mdns.d/ftp.service -- mDNS-SD advertisement of FTP service name Troglobit FTP Server type _ftp._tcp port 21 txt server=uftpd txt version=2.6 target ftp.luthien.local cname ftp.local _HTTP service example:_ # /etc/mdns.d/http.service -- mDNS-SD advertisement of HTTP service name Troglobit HTTP Server type _http._tcp port 80 txt server=merecat txt version=2.31 target www.luthien.local cname home.local _SSH service example:_ # /etc/mdns.d/http.service -- mDNS-SD advertisement of SSH service name Dropbear SSH Server type _ssh._tcp port 22 Build & Install --------------- This project is built for and developed on GNU/Linux systems, but should work on any UNIX[^1] like system. Use the standard GNU configure script to create a Makefile for your system and then call make. ./configure make all make install Users who checked out the source from GitHub must run `./autogen.sh` first to create the configure script. This requires GNU autotools and `pkg-config` to be installed on the build system. For the test suite you also need `libcmocka-dev`. If you install to the default location used by the configure script, the library is installed in `/usr/local/lib`, which may not be in the default search path for your system. Depending on the C library used, the file `/etc/ld.so.conf` may exist (there may also be a sub-directory). If `/usr/local/lib` is already listed there, you may need to update the cache: ldconfig -v |grep mdnsd If you don't get any output from the above command, the ld.so.conf needs updating, or you may not be using the GNU C library. [^1]: Builds and runs fine on: FreeBSD, NetBSD, OpenBSD, DragonFly BSD, and Illumos/SmartOS. Origin & References ------------------- This mDNS-SD implementation was developed by [Jeremie Miller][jeremie] in 2003, originally [announced on the rendezvous-dev][announced] mailing list. The original name was 'mhttp'. It has many forks and has been used by many other applications over the years. The GitHub project is an attempt by [Joachim Wiberg][troglobit] to clean up the original code base, develop it further, and maintain it for the long haul under the name `mdnsd`. [jeremie]: https://github.com/quartzjer [troglobit]: https://github.com/troglobit [releases]: https://github.com/troglobit/mdnsd/releases [announced]: https://web.archive.org/web/20140115142008/http://lists.apple.com/archives/rendezvous-dev/2003/Feb/msg00062.html [API.md]: https://github.com/troglobit/mdnsd/blob/master/API.md [License]: https://en.wikipedia.org/wiki/BSD_licenses [License svg]: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg [Repology]: https://repology.org/project/mdnsd/versions [Repology svg]: https://repology.org/badge/tiny-repos/mdnsd.svg [GitHub]: https://github.com/troglobit/mdnsd/actions/workflows/build.yml/ [GitHub svg]: https://github.com/troglobit/mdnsd/actions/workflows/build.yml/badge.svg [Coverity]: https://scan.coverity.com/projects/20680 [Coverity svg]: https://scan.coverity.com/projects/20680/badge.svg mdnsd-0.12/autogen.sh000077500000000000000000000000541436331001000145300ustar00rootroot00000000000000#!/bin/sh autoreconf -W portability -visfm mdnsd-0.12/configure.ac000066400000000000000000000115051436331001000150200ustar00rootroot00000000000000# Use v2.61 for backwards compat with Ubuntu 12.04 LTS AC_PREREQ([2.61]) AC_INIT([mdnsd],[0.12],[https://github.com/troglobit/mdnsd/issues]) AC_CONFIG_AUX_DIR(aux) AM_INIT_AUTOMAKE([1.11 foreign dist-xz]) AM_SILENT_RULES([yes]) AC_CONFIG_SRCDIR(src/mdnsd.c) AC_CONFIG_HEADERS(config.h) AC_CONFIG_FILES([Makefile mdnsd.service examples/Makefile libmdnsd/Makefile man/Makefile src/Makefile test/Makefile test/src/Makefile]) AC_CONFIG_MACRO_DIR([m4]) # Define necessary build flags AC_USE_SYSTEM_EXTENSIONS AC_PROG_CC AC_PROG_INSTALL AM_PROG_AR LT_INIT PKG_PROG_PKG_CONFIG # Check for usually missing API's, which we can replace AC_REPLACE_FUNCS([pidfile strlcpy utimensat]) AC_CONFIG_LIBOBJ_DIR([lib]) AC_CHECK_HEADERS([net/if.h sys/param.h sys/socket.h netinet/in.h]) AC_CHECK_MEMBERS([struct ip_mreqn.imr_ifindex], [], [], [ #ifdef HAVE_NETINET_IN_H # include #endif ]) AC_MSG_CHECKING(for SO_BINDTODEVICE in net/if.h) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ #include ], [ int i = SO_BINDTODEVICE; ])],[ac_cv_have_so_bindtodevice=yes], [ac_cv_have_so_bindtodevice=no]) AS_IF([test "x$ac_cv_have_so_bindtodevice" = "xyes"],[ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_SO_BINDTODEVICE, 1, [Whether our sockets support SO_BINDTODEVICE])],[ AC_MSG_RESULT(no)]) # Solaris/Illumos has networking functions in a separate library AC_CHECK_LIB(socket, bind) # Options AC_ARG_ENABLE(tests, [AS_HELP_STRING([--enable-tests], [Enable tests, requires cmocka library])], [enable_tests="$enableval"], [enable_tests="no"]) AC_ARG_ENABLE(doc, AS_HELP_STRING([--disable-doc], [Disable installing documentation]),, [enable_doc=yes]) AC_ARG_WITH([systemd], [AS_HELP_STRING([--with-systemd=DIR], [Directory for systemd service files])],, [with_systemd=auto]) AC_ARG_WITH(mquery, AS_HELP_STRING([--without-mquery], [Build without mdns-scan like mquery tool]), [mquery=$withval], [mquery='yes']) AM_CONDITIONAL(DOC, [test "x$enable_doc" = "xyes"]) # Check where to install the systemd .service file AS_IF([test "x$with_systemd" = "xyes" -o "x$with_systemd" = "xauto"], [ def_systemd=$($PKG_CONFIG --variable=systemdsystemunitdir systemd) AS_IF([test "x$def_systemd" = "x"], [AS_IF([test "x$with_systemd" = "xyes"], [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])]) with_systemd=no], [with_systemd="$def_systemd"])]) AS_IF([test "x$with_systemd" != "xno"], [AC_SUBST([systemddir], [$with_systemd])]) AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemd" != "xno"]) AS_IF([test "x$mquery" != "xno"], with_mquery="yes", with_mquery="no") AM_CONDITIONAL([ENABLE_MQUERY], [test "x$with_mquery" != "xno"]) PKG_CHECK_EXISTS([cmocka >= 1.1.0], [cmocka_avail=yes], [cmocka_avail=no]) AS_IF([test "x$enable_tests" = "xyes" -a "x$cmocka_avail" = "xyes"], [ PKG_CHECK_MODULES([cmocka], [cmocka >= 1.1.0]) unit_tests=yes], [unit_tests=no]) AM_CONDITIONAL([ENABLE_TESTS], [test "x$enable_tests" != "xno"]) AM_CONDITIONAL([ENABLE_UNIT_TESTS], [test "x$cmocka_avail" != "xno"]) # Expand $sbindir early, into $SBINDIR, for systemd unit file # NOTE: This does *not* take prefix/exec_prefix override at "make # install" into account, unfortunately. test "x$prefix" = xNONE && prefix=$ac_default_prefix test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' SYSCONFDIR=`eval echo $sysconfdir` SYSCONFDIR=`eval echo $SYSCONFDIR` AC_SUBST(SYSCONFDIR) BINDIR=`eval echo $bindir` BINDIR=`eval echo $BINDIR` AC_SUBST(BINDIR) SBINDIR=`eval echo $sbindir` SBINDIR=`eval echo $SBINDIR` AC_SUBST(SBINDIR) # Workaround for autoconf < 2.70, although some major distros have # backported support for runstatedir already. AS_IF([test -z "$runstatedir"], runstatedir="$localstatedir/run") AC_SUBST(runstatedir) # Create all config files AC_OUTPUT # Expand directories for configuration summary, unexpanded defaults: # sysconfdir => ${prefix}/etc # runstatedir => ${localstatedir}/run SYSCONFDIR=`eval echo $sysconfdir` LOCALSTATEDIR=`eval echo $localstatedir` RUNSTATEDIR=`eval echo $runstatedir` RUNSTATEDIR=`eval echo $RUNSTATEDIR` cat < #include /* utimensat() */ #include /* utimensat() on *BSD */ #include #include #include #include #include #ifndef HAVE_UTIMENSAT int utimensat(int dirfd, const char *pathname, const struct timespec ts[2], int flags); #endif static char *pidfile_path = NULL; static pid_t pidfile_pid = 0; static void pidfile_cleanup(void); const char *__pidfile_path = _PIDFILEDIR; const char *__pidfile_name = NULL; int pidfile(const char *basename) { int save_errno; int atexit_already; pid_t pid; FILE *f; if (basename == NULL) { errno = EINVAL; return (-1); } pid = getpid(); atexit_already = 0; if (pidfile_path != NULL) { if (!access(pidfile_path, R_OK) && pid == pidfile_pid) { utimensat(0, pidfile_path, NULL, 0); return (0); } free(pidfile_path); pidfile_path = NULL; __pidfile_name = NULL; atexit_already = 1; } if (basename[0] != '/') { if (asprintf(&pidfile_path, "%s/%s.pid", __pidfile_path, basename) == -1) return (-1); } else { if (asprintf(&pidfile_path, "%s", basename) == -1) return (-1); } if ((f = fopen(pidfile_path, "w")) == NULL) { save_errno = errno; free(pidfile_path); pidfile_path = NULL; errno = save_errno; return (-1); } if (fprintf(f, "%ld\n", (long)pid) <= 0 || fflush(f) != 0) { save_errno = errno; (void) fclose(f); (void) unlink(pidfile_path); free(pidfile_path); pidfile_path = NULL; errno = save_errno; return (-1); } (void) fclose(f); __pidfile_name = pidfile_path; /* * LITE extension, no need to set up another atexit() handler * if user only called us to update the mtime of the PID file */ if (atexit_already) return (0); pidfile_pid = pid; if (atexit(pidfile_cleanup) < 0) { save_errno = errno; (void) unlink(pidfile_path); free(pidfile_path); pidfile_path = NULL; pidfile_pid = 0; errno = save_errno; return (-1); } return (0); } static void pidfile_cleanup(void) { if (pidfile_path != NULL && pidfile_pid == getpid()) { (void) unlink(pidfile_path); free(pidfile_path); pidfile_path = NULL; } } mdnsd-0.12/lib/strlcpy.c000066400000000000000000000030771436331001000151510ustar00rootroot00000000000000/* $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $ */ /* * Copyright (c) 1998, 2015 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include /* * Copy string src to buffer dst of size dsize. At most dsize-1 * chars will be copied. Always NUL terminates (unless dsize == 0). * Returns strlen(src); if retval >= dsize, truncation occurred. */ size_t strlcpy(char *dst, const char *src, size_t dsize) { const char *osrc = src; size_t nleft = dsize; /* Copy as many bytes as will fit. */ if (nleft != 0) { while (--nleft != 0) { if ((*dst++ = *src++) == '\0') break; } } /* Not enough room in dst, add NUL and traverse rest of src. */ if (nleft == 0) { if (dsize != 0) *dst = '\0'; /* NUL-terminate dst */ while (*src++) ; } return(src - osrc - 1); /* count does not include NUL */ } mdnsd-0.12/lib/utimensat.c000066400000000000000000000026351436331001000154610ustar00rootroot00000000000000/* Replacement in case utimensat(2) is missing * * Copyright (C) 2017-2018 Joachim Wiberg * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.h" #include #ifdef HAVE_FCNTL_H #include #endif #include /* lutimes(), utimes(), utimensat() */ int utimensat(int dirfd, const char *pathname, const struct timespec ts[2], int flags) { int ret = -1; struct timeval tv[2]; if (dirfd != 0) { errno = ENOTSUP; return -1; } TIMESPEC_TO_TIMEVAL(&tv[0], &ts[0]); TIMESPEC_TO_TIMEVAL(&tv[1], &ts[1]); #ifdef AT_SYMLINK_NOFOLLOW if ((flags & AT_SYMLINK_NOFOLLOW) == AT_SYMLINK_NOFOLLOW) ret = lutimes(pathname, tv); else #endif ret = utimes(pathname, tv); return ret; } mdnsd-0.12/libmdnsd/000077500000000000000000000000001436331001000143245ustar00rootroot00000000000000mdnsd-0.12/libmdnsd/.gitignore000066400000000000000000000000211436331001000163050ustar00rootroot00000000000000*~ *.o libmdnsd* mdnsd-0.12/libmdnsd/1035.c000066400000000000000000000330611436331001000150630ustar00rootroot00000000000000/* Standalone DNS parsing, RFC10350 * * Copyright (c) 2003 Jeremie Miller * Copyright (c) 2016-2022 Joachim Wiberg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "1035.h" #include #include unsigned short int net2short(unsigned char **bufp) { unsigned short int i; i = **bufp; i <<= 8; i |= *(*bufp + 1); *bufp += 2; return i; } unsigned long int net2long(unsigned char **bufp) { long int l; l = **bufp; l <<= 8; l |= *(*bufp + 1); l <<= 8; l |= *(*bufp + 2); l <<= 8; l |= *(*bufp + 3); *bufp += 4; return l; } void short2net(unsigned short int i, unsigned char **bufp) { *(*bufp + 1) = (unsigned char)i; i >>= 8; **bufp = (unsigned char)i; *bufp += 2; } void long2net(unsigned long int l, unsigned char **bufp) { *(*bufp + 3) = (unsigned char)l; l >>= 8; *(*bufp + 2) = (unsigned char)l; l >>= 8; *(*bufp + 1) = (unsigned char)l; l >>= 8; **bufp = (unsigned char)l; *bufp += 4; } static unsigned short int _ldecomp(const char *ptr) { unsigned short int i; i = 0xc0 ^ ptr[0]; i <<= 8; i |= (unsigned char)ptr[1]; if (i >= 4096) i = 4095; return i; } static int _label(struct message *m, unsigned char **bufp, char **namep) { int x; char *label, *name; /* Sanity check */ if (m->_len > (int)sizeof(m->_packet)) return 1; /* Set namep to the end of the block */ *namep = name = (char *)m->_packet + m->_len; /* Loop storing label in the block */ for (label = (char *)*bufp; *label != 0; name += *label + 1, label += *label + 1) { /* Skip past any compression pointers, kick out if end encountered (bad data prolly) */ int prevOffset = -1; while (*label & 0xc0) { unsigned short int offset = _ldecomp(label); if (offset <= prevOffset || offset > m->_len) return 1; if (*(label = (char *)m->_buf + offset) == 0) break; prevOffset = offset; } /* Make sure we're not over the limits */ if ((name + *label) - *namep > 255 || m->_len + ((name + *label) - *namep) >= MAX_PACKET_LEN) return 1; /* Copy chars for this label */ memcpy(name, label + 1, (size_t)*label); name[(size_t)*label] = '.'; } /* Advance buffer */ for (label = (char *)*bufp; *label != 0 && !(*label & 0xc0 && label++); label += *label + 1) ; *bufp = (unsigned char *)(label + 1); /* Terminate name and check for cache or cache it */ *name = '\0'; for (x = 0; x < MAX_NUM_LABELS && m->_labels[x]; x++) { if (strcmp(*namep, m->_labels[x])) continue; *namep = m->_labels[x]; return 0; } /* No cache, so cache it if room */ if (x < MAX_NUM_LABELS && m->_labels[x] == 0) m->_labels[x] = *namep; m->_len += (int)(name - *namep) + 1; return 0; } /* Internal label matching */ static int _lmatch(const struct message *m, const char *l1, const char *l2) { int len; /* Always ensure we get called w/o a pointer */ if (*l1 & 0xc0) return _lmatch(m, (char *)m->_buf + _ldecomp(l1), l2); if (*l2 & 0xc0) return _lmatch(m, l1, (char *)m->_buf + _ldecomp(l2)); /* Same already? */ if (l1 == l2) return 1; /* Compare all label characters */ if (*l1 != *l2) return 0; for (len = 1; len <= *l1; len++) { if (l1[len] != l2[len]) return 0; } /* Get new labels */ l1 += *l1 + 1; l2 += *l2 + 1; /* At the end, all matched */ if (*l1 == 0 && *l2 == 0) return 1; /* Try next labels */ return _lmatch(m, l1, l2); } /* Nasty, convert host into label using compression */ static int _host(struct message *m, unsigned char **bufp, const char *name) { char label[256], *l; int len = 0, x = 1, y = 0, last = 0; if (name == 0) return 0; /* Make our label */ while (name[y]) { if (name[y] == '.') { if (!name[y + 1]) break; label[last] = (char)(x - (last + 1)); last = x; } else { label[x] = name[y]; } if (x++ == 255) return 0; y++; } label[last] = (char)(x - (last + 1)); if (x == 1) x--; /* Special case, bad names, but handle correctly */ len = x + 1; label[x] = 0; /* Always terminate w/ a 0 */ /* Double-loop checking each label against all m->_labels for match */ for (x = 0; label[x]; x += label[x] + 1) { for (y = 0; y < MAX_NUM_LABELS && m->_labels[y]; y++) { if (_lmatch(m, label + x, m->_labels[y])) { /* Matching label, set up pointer */ l = label + x; short2net((unsigned char *)m->_labels[y] - m->_packet, (unsigned char **)&l); label[x] |= '\xc0'; len = x + 2; break; } } if (label[x] & 0xc0) break; } /* Copy into buffer, point there now */ memcpy(*bufp, label, len); l = (char *)*bufp; *bufp += len; /* For each new label, store it's location for future compression */ for (x = 0; l[x] && m->_label < MAX_NUM_LABELS; x += l[x] + 1) { if (l[x] & 0xc0) break; m->_labels[m->_label++] = l + x; } return len; } static int _rrparse(struct message *m, struct resource *rr, int count, unsigned char **bufp) { int i; for (i = 0; i < count; i++) { if (_label(m, bufp, &(rr[i].name))) return 1; rr[i].type = net2short(bufp); rr[i].class = net2short(bufp); rr[i].ttl = net2long(bufp); rr[i].rdlength = net2short(bufp); // fprintf(stderr, "Record type %d class 0x%2x ttl %lu len %d\n", rr[i].type, rr[i].class, rr[i].ttl, rr[i].rdlength); /* If not going to overflow, make copy of source rdata */ if (rr[i].rdlength + (*bufp - m->_buf) > MAX_PACKET_LEN || m->_len + rr[i].rdlength > MAX_PACKET_LEN) { rr[i].rdlength = 0; return 1; } /* For the following records the rdata will be parsed later. So don't set it here: * NS, CNAME, PTR, DNAME, SOA, MX, AFSDB, RT, KX, RP, PX, SRV, NSEC * See 18.14 of https://tools.ietf.org/html/rfc6762#page-47 */ if (rr[i].type == QTYPE_NS || rr[i].type == QTYPE_CNAME || rr[i].type == QTYPE_PTR || rr[i].type == QTYPE_SRV) { rr[i].rdlength = 0; } else { rr[i].rdata = m->_packet + m->_len; m->_len += rr[i].rdlength; memcpy(rr[i].rdata, *bufp, rr[i].rdlength); } /* Parse commonly known ones */ switch (rr[i].type) { case QTYPE_A: if (m->_len + INET_ADDRSTRLEN > MAX_PACKET_LEN) return 1; rr[i].known.a.name = (char *)m->_packet + m->_len; m->_len += INET_ADDRSTRLEN; inet_ntop(AF_INET, *bufp, rr[i].known.a.name, INET_ADDRSTRLEN); memcpy(&(rr[i].known.a.ip.s_addr), *bufp, sizeof(rr[i].known.a.ip.s_addr)); *bufp += sizeof(rr[i].known.a.ip.s_addr); break; case QTYPE_AAAA: if (m->_len + INET6_ADDRSTRLEN > MAX_PACKET_LEN) return 1; rr[i].known.aaaa.name = (char *)m->_packet + m->_len; m->_len += INET6_ADDRSTRLEN; inet_ntop(AF_INET6, *bufp, rr[i].known.aaaa.name, INET6_ADDRSTRLEN); memcpy(rr[i].known.aaaa.ip6.s6_addr, *bufp, sizeof(rr[i].known.aaaa.ip6.s6_addr)); *bufp += sizeof(rr[i].known.aaaa.ip6.s6_addr); break; case QTYPE_NS: if (_label(m, bufp, &(rr[i].known.ns.name))) return 1; break; case QTYPE_CNAME: if (_label(m, bufp, &(rr[i].known.cname.name))) return 1; break; case QTYPE_PTR: if (_label(m, bufp, &(rr[i].known.ptr.name))) return 1; break; case QTYPE_SRV: rr[i].known.srv.priority = net2short(bufp); rr[i].known.srv.weight = net2short(bufp); rr[i].known.srv.port = net2short(bufp); if (_label(m, bufp, &(rr[i].known.srv.name))) return 1; break; case QTYPE_TXT: default: *bufp += rr[i].rdlength; } } return 0; } /* Keep all our mem in one (aligned) block for easy freeing */ #define my(x,y) \ while (m->_len & 7) \ m->_len++; \ (x) = (void *)(m->_packet + m->_len); \ m->_len += (y); int message_parse(struct message *m, unsigned char *packet) { int i; unsigned char *buf; if (packet == 0 || m == 0) return 1; /* Header stuff bit crap */ m->_buf = buf = packet; m->id = net2short(&buf); if (buf[0] & 0x80) m->header.qr = 1; m->header.opcode = (buf[0] & 0x78) >> 3; if (buf[0] & 0x04) m->header.aa = 1; if (buf[0] & 0x02) m->header.tc = 1; if (buf[0] & 0x01) m->header.rd = 1; if (buf[1] & 0x80) m->header.ra = 1; m->header.z = (buf[1] & 0x70) >> 4; m->header.rcode = buf[1] & 0x0F; buf += 2; m->qdcount = net2short(&buf); if (m->_len + (sizeof(struct question) * m->qdcount) > MAX_PACKET_LEN - 8) { m->qdcount = 0; return 1; } m->ancount = net2short(&buf); if (m->_len + (sizeof(struct resource) * m->ancount) > MAX_PACKET_LEN - 8) { m->ancount = 0; return 1; } m->nscount = net2short(&buf); if (m->_len + (sizeof(struct resource) * m->nscount) > MAX_PACKET_LEN - 8) { m->nscount = 0; return 1; } m->arcount = net2short(&buf); if (m->_len + (sizeof(struct resource) * m->arcount) > MAX_PACKET_LEN - 8) { m->arcount = 0; return 1; } /* Process questions */ my(m->qd, sizeof(struct question) * m->qdcount); for (i = 0; i < m->qdcount; i++) { if (_label(m, &buf, &(m->qd[i].name))) return 1; m->qd[i].type = net2short(&buf); m->qd[i].class = net2short(&buf); } /* Process rrs */ my(m->an, sizeof(struct resource) * m->ancount); my(m->ns, sizeof(struct resource) * m->nscount); my(m->ar, sizeof(struct resource) * m->arcount); if (_rrparse(m, m->an, m->ancount, &buf)) return 1; if (_rrparse(m, m->ns, m->nscount, &buf)) return 1; if (_rrparse(m, m->ar, m->arcount, &buf)) return 1; return 0; } void message_qd(struct message *m, char *name, unsigned short int type, unsigned short int class) { m->qdcount++; if (m->_buf == 0) m->_buf = m->_packet + 12; _host(m, &(m->_buf), name); short2net(type, &(m->_buf)); short2net(class, &(m->_buf)); } static void _rrappend(struct message *m, char *name, unsigned short int type, unsigned short int class, unsigned long int ttl) { if (m->_buf == 0) m->_buf = m->_packet + 12; _host(m, &(m->_buf), name); short2net(type, &(m->_buf)); short2net(class, &(m->_buf)); long2net(ttl, &(m->_buf)); } void message_an(struct message *m, char *name, unsigned short int type, unsigned short int class, unsigned long int ttl) { m->ancount++; _rrappend(m, name, type, class, ttl); } void message_ns(struct message *m, char *name, unsigned short int type, unsigned short int class, unsigned long int ttl) { m->nscount++; _rrappend(m, name, type, class, ttl); } void message_ar(struct message *m, char *name, unsigned short int type, unsigned short int class, unsigned long int ttl) { m->arcount++; _rrappend(m, name, type, class, ttl); } void message_rdata_long(struct message *m, unsigned long l) { short2net(4, &(m->_buf)); long2net(l, &(m->_buf)); } void message_rdata_ipv4(struct message *m, struct in_addr a) { short2net(4, &(m->_buf)); memcpy(m->_buf, &a.s_addr, 4); m->_buf += 4; } void message_rdata_ipv6(struct message *m, struct in6_addr a6) { short2net(16, &(m->_buf)); memcpy(m->_buf, a6.s6_addr, 16); m->_buf += 16; } void message_rdata_name(struct message *m, char *name) { unsigned char *mybuf = m->_buf; m->_buf += 2; short2net(_host(m, &(m->_buf), name), &mybuf); } void message_rdata_srv(struct message *m, unsigned short int priority, unsigned short int weight, unsigned short int port, char *name) { unsigned char *mybuf = m->_buf; m->_buf += 2; short2net(priority, &(m->_buf)); short2net(weight, &(m->_buf)); short2net(port, &(m->_buf)); short2net(_host(m, &(m->_buf), name) + 6, &mybuf); } void message_rdata_raw(struct message *m, unsigned char *rdata, unsigned short int rdlength) { if ((m->_buf - m->_packet) + rdlength > 4096) rdlength = 0; short2net(rdlength, &(m->_buf)); memcpy(m->_buf, rdata, rdlength); m->_buf += rdlength; } unsigned char *message_packet(struct message *m) { unsigned char c, *buf = m->_buf; m->_buf = m->_packet; short2net(m->id, &(m->_buf)); if (m->header.qr) m->_buf[0] |= 0x80; if ((c = m->header.opcode)) m->_buf[0] |= (c << 3); if (m->header.aa) m->_buf[0] |= 0x04; if (m->header.tc) m->_buf[0] |= 0x02; if (m->header.rd) m->_buf[0] |= 0x01; if (m->header.ra) m->_buf[1] |= 0x80; if ((c = m->header.z)) m->_buf[1] |= (c << 4); if (m->header.rcode) m->_buf[1] |= m->header.rcode; m->_buf += 2; short2net(m->qdcount, &(m->_buf)); short2net(m->ancount, &(m->_buf)); short2net(m->nscount, &(m->_buf)); short2net(m->arcount, &(m->_buf)); m->_buf = buf; /* Restore, so packet_len works */ return m->_packet; } int message_packet_len(struct message *m) { if (m->_buf == 0) return 12; return (int)(m->_buf - m->_packet); } mdnsd-0.12/libmdnsd/1035.h000066400000000000000000000134651436331001000150760ustar00rootroot00000000000000/* Standalone DNS parsing, RFC10350 * * Copyright (c) 2003 Jeremie Miller * Copyright (c) 2016-2022 Joachim Wiberg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* Familiarize yourself with RFC1035 if you want to know what all the * variable names mean. This file hides most of the dirty work all of * this code depends on the buffer space a packet is in being 4096 and * zero'd before the packet is copied in also conveniently decodes srv * rr's, type 33, see RFC2782 */ #ifndef MDNS_1035_H_ #define MDNS_1035_H_ #include "config.h" #include #ifdef HAVE_NETINET_IN_H # include #endif #ifdef HAVE_SYS_SOCKET_H # include #endif /* * At least FreeBSD, OpenBSD and NexentaCore do not define the * s6_addr32, s6_addr8, and s6_addr16 for user land. */ #if !defined s6_addr32 && defined __sun__ # define s6_addr32 _S6_un._S6_u32 #elif !defined s6_addr32 && ( defined __OpenBSD__ || defined __FreeBSD__ || defined __NetBSD__ ) # define s6_addr32 __u6_addr.__u6_addr32 #endif /* !defined s6_addr32 */ /* Should be reasonably large, for UDP */ #define MAX_PACKET_LEN 65535 #define MAX_NUM_LABELS 512 struct question { char *name; unsigned short int type, class; }; #define QTYPE_A 1 #define QTYPE_NS 2 #define QTYPE_CNAME 5 #define QTYPE_PTR 12 #define QTYPE_TXT 16 #define QTYPE_AAAA 28 #define QTYPE_SRV 33 #define QTYPE_ANY 255 struct resource { char *name; unsigned short int type, class; unsigned long int ttl; unsigned short int rdlength; unsigned char *rdata; union { struct { struct in_addr ip; char *name; } a; struct { struct in6_addr ip6; char *name; } aaaa; struct { char *name; } ns; struct { char *name; } cname; struct { char *name; } ptr; struct { unsigned short int priority, weight, port; char *name; } srv; } known; }; struct message { /* External data */ unsigned short int id; struct { unsigned short qr:1, opcode:4, aa:1, tc:1, rd:1, ra:1, z:3, rcode:4; } header; unsigned short int qdcount, ancount, nscount, arcount; struct question *qd; struct resource *an, *ns, *ar; /* Internal variables */ unsigned char *_buf; char *_labels[MAX_NUM_LABELS]; int _len, _label; /* Packet acts as padding, easier mem management */ unsigned char _packet[MAX_PACKET_LEN]; }; /** * Returns the next short/long off the buffer (and advances it) */ unsigned short int net2short(unsigned char **buf); unsigned long int net2long (unsigned char **buf); /** * copies the short/long into the buffer (and advances it) */ void short2net(unsigned short int i, unsigned char **buf); void long2net (unsigned long int l, unsigned char **buf); /** * parse packet into message, packet must be at least MAX_PACKET_LEN and * message must be zero'd for safety * @returns 0 if OK, else parser error. */ int message_parse(struct message *m, unsigned char *packet); /** * create a message for sending out on the wire */ struct message *message_wire(void); /** * append a question to the wire message */ void message_qd(struct message *m, char *name, unsigned short int type, unsigned short int class); /** * append a resource record to the message, all called in order! */ void message_an(struct message *m, char *name, unsigned short int type, unsigned short int class, unsigned long int ttl); void message_ns(struct message *m, char *name, unsigned short int type, unsigned short int class, unsigned long int ttl); void message_ar(struct message *m, char *name, unsigned short int type, unsigned short int class, unsigned long int ttl); /** * Append various special types of resource data blocks */ void message_rdata_long (struct message *m, unsigned long l); void message_rdata_ipv4 (struct message *m, struct in_addr a); void message_rdata_ipv6 (struct message *m, struct in6_addr a6); void message_rdata_name (struct message *m, char *name); void message_rdata_srv (struct message *m, unsigned short int priority, unsigned short int weight, unsigned short int port, char *name); void message_rdata_raw (struct message *m, unsigned char *rdata, unsigned short int rdlength); /** * Return the wire format (and length) of the message, just free message * when done */ unsigned char *message_packet (struct message *m); int message_packet_len (struct message *m); #endif /* MDNS_1035_H_ */ mdnsd-0.12/libmdnsd/Makefile.am000066400000000000000000000005271436331001000163640ustar00rootroot00000000000000lib_LTLIBRARIES = libmdnsd.la libmdnsd_la_SOURCES = mdnsd.c mdnsd.h log.c 1035.c 1035.h sdtxt.c sdtxt.h xht.c xht.h libmdnsd_la_CFLAGS = -std=gnu99 -W -Wall -Wextra -Wno-unused-parameter -Wno-unused-function libmdnsd_la_CPPFLAGS = -D_GNU_SOURCE -D_BSD_SOURCE -D_DEFAULT_SOURCE libmdnsd_la_LDFLAGS = $(AM_LDFLAGS) -version-info 1:0:0 mdnsd-0.12/libmdnsd/log.c000066400000000000000000000104301436331001000152470ustar00rootroot00000000000000/* * Copyright (c) 2018-2022 Joachim Wiberg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #define SYSLOG_NAMES #include #include #include #include #include #include #include #include #include #ifndef MAX #define MAX(x,y) ((x) > (y) ? (x) : (y)) #endif #ifndef INTERNAL_NOPRI /* Illumos/SmartOS/Bionic */ #define INTERNAL_NOPRI 0x10 struct { char *c_name; int c_val; } prioritynames[] = { { "alert", LOG_ALERT }, { "crit", LOG_CRIT }, { "debug", LOG_DEBUG }, { "emerg", LOG_EMERG }, { "error", LOG_ERR }, { "err", LOG_ERR }, { "info", LOG_INFO }, { "none", INTERNAL_NOPRI }, { "notice", LOG_NOTICE }, { "panic", LOG_EMERG }, { "warning", LOG_WARNING }, { "warn", LOG_WARNING }, { NULL, -1 } }; #endif static int do_syslog = 0; static int loglevel = LOG_NOTICE; int mdnsd_log_level(char *level) { int lvl = -1; for (int i = 0; prioritynames[i].c_name; i++) { size_t len = MAX(strlen(prioritynames[i].c_name), strlen(level)); if (!strncasecmp(prioritynames[i].c_name, level, len)) { lvl = prioritynames[i].c_val; break; } } if (-1 == lvl) lvl = atoi(level); if (lvl >= 0) loglevel = lvl; if (do_syslog) setlogmask(LOG_UPTO(loglevel)); return lvl; } void mdnsd_log_open(const char *ident) { openlog(ident, LOG_PID | LOG_NDELAY, LOG_DAEMON); setlogmask(LOG_UPTO(loglevel)); do_syslog = 1; } void mdnsd_log_hex(char *msg, unsigned char *buffer, ssize_t len) { char ascii[17]; int i; if (do_syslog) return; if (loglevel < LOG_DEBUG) return; printf("%s", msg); memset(ascii, 0, sizeof(ascii)); for (i = 0; i < len; i++) { if (i % 16 == 0) printf("%s\n%06x ", ascii, i); printf("%02X ", buffer[i]); if (isprint((int)(buffer[i]))) ascii[i%16] = buffer[i]; else ascii[i%16] = '.'; } ascii[i % 16] = 0; while (i % 16) { printf(" "); i++; } printf("%s\n", ascii); printf("\n"); } void mdnsd_log(int severity, const char *fmt, ...) { FILE *file; va_list args; if (loglevel == INTERNAL_NOPRI) return; if (severity > LOG_WARNING) file = stdout; else file = stderr; va_start(args, fmt); if (do_syslog) vsyslog(severity, fmt, args); else if (severity <= loglevel) { vfprintf(file, fmt, args); fprintf(file, "\n"); fflush(file); } va_end(args); } void mdnsd_log_time(struct timeval *tv, char *buf, size_t len) { char tmp[15]; time_t t; struct tm *tm; if (loglevel < LOG_DEBUG) return; t = tv->tv_sec; tm = localtime(&t); tm->tm_sec += tv->tv_usec / 1000000; if (buf && len > 8) { strftime(buf, len, "%H:%M:%S", tm); return; } strftime(tmp, sizeof(tmp), "%H:%M:%S", tm); mdnsd_log(LOG_DEBUG, "@%s", tmp); } mdnsd-0.12/libmdnsd/mdnsd.c000066400000000000000000001043741436331001000156060ustar00rootroot00000000000000/* * Copyright (c) 2003 Jeremie Miller * Copyright (c) 2016-2022 Joachim Wiberg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "mdnsd.h" #include #include #include #include #include #define SPRIME 109 /* Size of query/publish hashes */ #define LPRIME 1009 /* Size of cache hash */ #define GC 86400 /* Brute force garbage cleanup * frequency, rarely needed (daily * default) */ /** * Messy, but it's the best/simplest balance I can find at the moment * * Some internal data types, and a few hashes: querys, answers, cached, * and records (published, unique and shared). Each type has different * semantics for processing, both for timeouts, incoming, and outgoing * I/O. They inter-relate too, like records affect the querys they are * relevant to. Nice things about MDNS: we only publish once (and then * ask asked), and only query once, then just expire records we've got * cached */ struct query { char *name; int type; unsigned long int nexttry; int tries; int (*answer)(mdns_answer_t *, void *); void *arg; struct query *next, *list; }; struct unicast { int id; struct in_addr to; unsigned short port; mdns_record_t *r; struct unicast *next; }; struct cached { struct mdns_answer rr; struct query *q; struct cached *next; }; struct mdns_record { struct mdns_answer rr; char unique; /* # of checks performed to ensure */ int modified; /* Ignore conflicts after update at runtime */ int tries; void (*conflict)(char *, int, void *); void *arg; struct timeval last_sent; struct mdns_record *next, *list; }; struct mdns_daemon { char shutdown, disco; unsigned long int expireall, checkqlist; struct timeval now, sleep, pause, probe, publish; int class, frame; struct cached *cache[LPRIME]; struct mdns_record *published[SPRIME], *probing, *a_now, *a_pause, *a_publish; struct unicast *uanswers; struct query *queries[SPRIME], *qlist; struct in_addr addr; struct in6_addr addr_v6; mdnsd_record_received_callback received_callback; void *received_callback_data; }; static int _namehash(const char *s) { const unsigned char *name = (const unsigned char *)s; unsigned long h = 0, g; while (*name) { /* do some fancy bitwanking on the string */ h = (h << 4) + (unsigned long)(*name++); if ((g = (h & 0xF0000000UL)) != 0) h ^= (g >> 24); h &= ~g; } return (int)h; } /* Basic linked list and hash primitives */ static struct query *_q_next(mdns_daemon_t *d, struct query *q, const char *host, int type) { if (!q) q = d->queries[_namehash(host) % SPRIME]; else q = q->next; for (; q != 0; q = q->next) { if (q->type == type && strcmp(q->name, host) == 0) return q; } return NULL; } static struct cached *_c_next(mdns_daemon_t *d, struct cached *c,const char *host, int type) { if (!c) c = d->cache[_namehash(host) % LPRIME]; else c = c->next; for (; c != 0; c = c->next) { if ((type == c->rr.type || type == QTYPE_ANY) && strcmp(c->rr.name, host) == 0) return c; } return NULL; } static mdns_record_t *_r_next(mdns_daemon_t *d, mdns_record_t *r, const char *host, int type) { if (!r) r = d->published[_namehash(host) % SPRIME]; else r = r->next; for (; r != NULL; r = r->next) { if ((type == r->rr.type || type == QTYPE_ANY) && strcmp(r->rr.name, host) == 0) return r; } return NULL; } static size_t _rr_len(mdns_answer_t *rr) { size_t len = 12; /* name is always compressed (dup of earlier), plus normal stuff */ if (rr->rdata) len += rr->rdlen; if (rr->rdname) len += strlen(rr->rdname); /* worst case */ if (rr->ip.s_addr) len += 4; if (! IN6_IS_ADDR_UNSPECIFIED(&(rr->ip6))) len += 16; if (rr->type == QTYPE_PTR) len += 6; /* srv record stuff */ return len; } /* Compares new rdata with known a, painfully */ static bool _a_match(struct resource *r, mdns_answer_t *a) { if (!a->name) return 0; if (strcmp(r->name, a->name) || r->type != a->type) return 0; switch (r->type) { case QTYPE_SRV: return r->known.srv.name && a->rdname && strcmp(r->known.srv.name, a->rdname) == 0 && a->srv.port == r->known.srv.port && a->srv.weight == r->known.srv.weight && a->srv.priority == r->known.srv.priority; case QTYPE_PTR: case QTYPE_NS: case QTYPE_CNAME: return strcmp(a->rdname, r->known.ns.name) == 0; case QTYPE_A: return memcmp(&r->known.a.ip, &a->ip, 4) == 0; case QTYPE_AAAA: return memcmp(&r->known.aaaa.ip6, &a->ip6, 16) == 0; default: return r->rdlength == a->rdlen && memcmp(r->rdata, a->rdata, r->rdlength) == 0; } return 0; } /* Compare time values easily */ static long _tvdiff(struct timeval old, struct timeval new) { long udiff = 0; if (old.tv_sec != new.tv_sec) udiff = (new.tv_sec - old.tv_sec) * 1000000; return (new.tv_usec - old.tv_usec) + udiff; } static void _r_remove_list(mdns_record_t **list, mdns_record_t *r) { mdns_record_t *tmp; if (*list == r) { *list = r->list; r->list = NULL; return; } for (tmp = *list; tmp; tmp = tmp->list) { if (tmp->list == r) { tmp->list = r->list; r->list = NULL; break; } if (tmp == tmp->list) break; } } static void _r_remove_lists(mdns_daemon_t *d, mdns_record_t *r, mdns_record_t **skip) { if (d->probing && &d->probing != skip) _r_remove_list(&d->probing, r); if (d->a_now && &d->a_now != skip) _r_remove_list(&d->a_now, r); if (d->a_pause && &d->a_pause != skip) _r_remove_list(&d->a_pause, r); if (d->a_publish && &d->a_publish != skip) _r_remove_list(&d->a_publish, r); } /* Make sure not already on the list, then insert */ static void _r_push(mdns_record_t **list, mdns_record_t *r) { mdns_record_t *cur; for (cur = *list; cur != 0; cur = cur->list) { if (cur == r) return; } r->list = *list; *list = r; } /* Force any r out right away, if valid */ static void _r_publish(mdns_daemon_t *d, mdns_record_t *r) { r->modified = 1; if (r->unique && r->unique < 5) return; /* Probing already */ r->tries = 0; d->publish.tv_sec = d->now.tv_sec; d->publish.tv_usec = d->now.tv_usec; /* check if r already in other lists. If yes, remove it from there */ _r_remove_lists(d, r, &d->a_publish); _r_push(&d->a_publish, r); } /* send r out asap */ static void _r_send(mdns_daemon_t *d, mdns_record_t *r) { /* Being published, make sure that happens soon */ if (r->tries < 4) { d->publish.tv_sec = d->now.tv_sec; d->publish.tv_usec = d->now.tv_usec; return; } /* Known unique ones can be sent asap */ if (r->unique) { /* check if r already in other lists. If yes, remove it from there */ _r_remove_lists(d, r, &d->a_now); _r_push(&d->a_now, r); return; } /* Set d->pause.tv_usec to random 20-120 msec */ d->pause.tv_sec = d->now.tv_sec; d->pause.tv_usec = d->now.tv_usec + (d->now.tv_usec % 100) + 20; /* check if r already in other lists. If yes, remove it from there */ _r_remove_lists(d, r, &d->a_pause); _r_push(&d->a_pause, r); } /* Create generic unicast response struct */ static void _u_push(mdns_daemon_t *d, mdns_record_t *r, int id, struct in_addr to, unsigned short port) { struct unicast *u; u = calloc(1, sizeof(struct unicast)); if (!u) return; u->r = r; u->id = id; u->to = to; u->port = port; u->next = d->uanswers; d->uanswers = u; } static void _q_reset(mdns_daemon_t *d, struct query *q) { struct cached *cur = 0; q->nexttry = 0; q->tries = 0; while ((cur = _c_next(d, cur, q->name, q->type))) { if (q->nexttry == 0 || cur->rr.ttl - 7 < q->nexttry) q->nexttry = cur->rr.ttl - 7; } if (q->nexttry != 0 && q->nexttry < d->checkqlist) d->checkqlist = q->nexttry; } /* No more queries, update all its cached entries, remove from lists */ static void _q_done(mdns_daemon_t *d, struct query *q) { struct cached *c = 0; struct query *cur; int i = _namehash(q->name) % LPRIME; while ((c = _c_next(d, c, q->name, q->type))) c->q = 0; if (d->qlist == q) { d->qlist = q->list; } else { for (cur = d->qlist; cur->list != q; cur = cur->list) ; cur->list = q->list; } if (d->queries[i] == q) { d->queries[i] = q->next; } else { for (cur = d->queries[i]; cur->next != q; cur = cur->next) ; cur->next = q->next; } free(q->name); free(q); } static void _free_cached(struct cached *c) { if (!c) return; if (c->rr.name) { free(c->rr.name); c->rr.name = NULL; } if (c->rr.rdata) { free(c->rr.rdata); c->rr.rdata = NULL; } if (c->rr.rdname) { free(c->rr.rdname); c->rr.rdname = NULL; } free(c); } static void _free_record(mdns_record_t *r) { if (!r) return; if (r->rr.name) { free(r->rr.name); r->rr.name = NULL; } if (r->rr.rdata) { free(r->rr.rdata); r->rr.rdata = NULL; } if (r->rr.rdname) { free(r->rr.rdname); r->rr.rdname = NULL; } free(r); } /* buh-bye, remove from hash and free */ static void _r_done(mdns_daemon_t *d, mdns_record_t *r) { mdns_record_t *cur = 0; int i; if (!r || !r->rr.name) return; i = _namehash(r->rr.name) % SPRIME; if (d->published[i] == r) { d->published[i] = r->next; } else { for (cur = d->published[i]; cur && cur->next != r; cur = cur->next) ; if (cur) cur->next = r->next; } _free_record(r); } /* Call the answer function with this cached entry */ static void _q_answer(mdns_daemon_t *d, struct cached *c) { if (c->rr.ttl <= (unsigned long)d->now.tv_sec) c->rr.ttl = 0; if (c->q->answer(&c->rr, c->q->arg) == -1) _q_done(d, c->q); } static void _conflict(mdns_daemon_t *d, mdns_record_t *r) { r->conflict(r->rr.name, r->rr.type, r->arg); mdnsd_done(d, r); } /* Expire any old entries in this list */ static void _c_expire(mdns_daemon_t *d, struct cached **list) { struct cached *cur = *list; struct cached *last = NULL; struct cached *next; while (cur) { next = cur->next; if ((unsigned long)d->now.tv_sec >= cur->rr.ttl) { if (last) last->next = next; /* Update list pointer if the first one expired */ if (*list == cur) *list = next; if (cur->q) _q_answer(d, cur); _free_cached(cur); } else { last = cur; } cur = next; } } /* Brute force expire any old cached records */ static void _gc(mdns_daemon_t *d) { int i; for (i = 0; i < LPRIME; i++) { if (d->cache[i]) _c_expire(d, &d->cache[i]); } d->expireall = (unsigned long)(d->now.tv_sec + GC); } static int _cache(mdns_daemon_t *d, struct resource *r, struct in_addr ip) { unsigned long int ttl; struct cached *c = 0; int i = _namehash(r->name) % LPRIME; /* Cache flush for unique entries */ if (r->class == 32768 + d->class) { while ((c = _c_next(d, c, r->name, r->type))) c->rr.ttl = 0; _c_expire(d, &d->cache[i]); } /* Process deletes */ if (r->ttl == 0) { while ((c = _c_next(d, c, r->name, r->type))) { if (_a_match(r, &c->rr)) { c->rr.ttl = 0; _c_expire(d, &d->cache[i]); c = NULL; } } return 0; } /* * XXX: The c->rr.ttl is a hack for now, BAD SPEC, start * retrying just after half-waypoint, then expire */ ttl = (unsigned long)d->now.tv_sec + (r->ttl / 2) + 8; /* If entry already exists, only udpate TTL value */ c = NULL; while ((c = _c_next(d, c, r->name, r->type))) { if (r->type == QTYPE_PTR && strcmp(c->rr.rdname, r->known.ns.name)) { continue; } c->rr.ttl = ttl; return 0; } /* New entry, cache it */ c = calloc(1, sizeof(struct cached)); if (!c) return 1; c->rr.name = strdup(r->name); if (!c->rr.name) { free(c); return 1; } c->rr.type = r->type; c->rr.ttl = ttl; c->rr.rdlen = r->rdlength; if (r->rdlength && !r->rdata) { // ERR("rdlength is %d but rdata is NULL for domain name %s, type: %d, ttl: %ld", r->rdlength, r->name, r->type, r->ttl); free(c->rr.name); free(c); return 1; } if (r->rdlength) { c->rr.rdata = malloc(r->rdlength); if (!c->rr.rdata) { free(c->rr.name); free(c); return 1; } memcpy(c->rr.rdata, r->rdata, r->rdlength); } else { c->rr.rdata = NULL; } switch (r->type) { case QTYPE_A: c->rr.ip = r->known.a.ip; break; case QTYPE_AAAA: c->rr.ip6 = r->known.aaaa.ip6; break; case QTYPE_NS: case QTYPE_CNAME: case QTYPE_PTR: c->rr.rdname = strdup(r->known.ns.name); c->rr.ip = ip; break; case QTYPE_SRV: c->rr.rdname = strdup(r->known.srv.name); c->rr.srv.port = r->known.srv.port; c->rr.srv.weight = r->known.srv.weight; c->rr.srv.priority = r->known.srv.priority; break; } c->next = d->cache[i]; d->cache[i] = c; if ((c->q = _q_next(d, 0, r->name, r->type))) _q_answer(d, c); return 0; } /* Copy the data bits only */ static void _a_copy(struct message *m, mdns_answer_t *a) { if (a->rdata) { message_rdata_raw(m, a->rdata, a->rdlen); return; } if (a->ip.s_addr) message_rdata_ipv4(m, a->ip); else if (!IN6_IS_ADDR_UNSPECIFIED(&(a->ip6))) message_rdata_ipv6(m, a->ip6); if (a->type == QTYPE_SRV) message_rdata_srv(m, a->srv.priority, a->srv.weight, a->srv.port, a->rdname); else if (a->rdname) message_rdata_name(m, a->rdname); } /* Copy a published record into an outgoing message */ static int _r_out(mdns_daemon_t *d, struct message *m, mdns_record_t **list) { mdns_record_t *r; int ret = 0; while ((r = *list) != NULL && message_packet_len(m) + (int)_rr_len(&r->rr) < d->frame) { if (r != r->list) *list = r->list; else *list = NULL; /* Service enumeration/discovery, drop non-PTR replies */ if (d->disco) { if (r->rr.type != QTYPE_PTR) continue; if (strcmp(r->rr.name, DISCO_NAME)) continue; } INFO("Appending name: %s, type %d to outbound message ...", r->rr.name, r->rr.type); ret++; if (r->unique) message_an(m, r->rr.name, r->rr.type, d->class + 32768, r->rr.ttl); else message_an(m, r->rr.name, r->rr.type, d->class, r->rr.ttl); r->last_sent = d->now; _a_copy(m, &r->rr); r->modified = 0; /* If updated we've now sent the update. */ if (r->rr.ttl == 0) { /* * also remove from other lists, because record * may be in multiple lists at the same time */ _r_remove_lists(d, r, list); _r_done(d, r); } } return ret; } mdns_daemon_t *mdnsd_new(int class, int frame) { mdns_daemon_t *d; d = calloc(1, sizeof(struct mdns_daemon)); if (!d) return NULL; gettimeofday(&d->now, 0); d->expireall = (unsigned long)d->now.tv_sec + GC; d->class = class; d->frame = frame; d->received_callback = NULL; return d; } void mdnsd_set_address(mdns_daemon_t *d, struct in_addr addr) { int i; if (!memcmp(&d->addr, &addr, sizeof(d->addr))) return; /* No change */ for (i = 0; i < SPRIME; i++) { mdns_record_t *r, *next; r = d->published[i]; while (r) { next = r->next; if (r->rr.type == QTYPE_A) { if (addr.s_addr == 0) { r->rr.ttl = 0; r->list = d->a_now; d->a_now = r; } else { mdnsd_set_ip(d, r, addr); } } r = next; } } d->addr = addr; } struct in_addr mdnsd_get_address(mdns_daemon_t *d) { return d->addr; } void mdnsd_set_ipv6_address(mdns_daemon_t *d, struct in6_addr addr) { int i; if (!memcmp(&d->addr_v6, &addr, sizeof(d->addr_v6))) return; /* No change */ for (i = 0; i < SPRIME; i++) { mdns_record_t *r, *next; r = d->published[i]; while (r) { next = r->next; if (r->rr.type == QTYPE_AAAA) { if (IN6_IS_ADDR_UNSPECIFIED(&addr)) { r->rr.ttl = 0; r->list = d->a_now; d->a_now = r; } else { mdnsd_set_ipv6(d, r, addr); } } r = next; } } d->addr_v6 = addr; } struct in6_addr mdnsd_get_ipv6_address(mdns_daemon_t *d) { return d->addr_v6; } /* Shutting down, zero out ttl and push out all records */ void mdnsd_shutdown(mdns_daemon_t *d) { int i; mdns_record_t *cur, *next; if (!d) return; d->a_now = 0; for (i = 0; i < SPRIME; i++) { for (cur = d->published[i]; cur != 0;) { next = cur->next; cur->rr.ttl = 0; cur->list = d->a_now; d->a_now = cur; cur = next; } } d->shutdown = 1; } void mdnsd_flush(mdns_daemon_t *d) { (void)d; /* - Set all querys to 0 tries * - Free whole cache * - Set all mdns_record_t *to probing * - Reset all answer lists */ } void mdnsd_free(mdns_daemon_t *d) { struct unicast *u; if (!d) return; for (size_t i = 0; i< LPRIME; i++) { struct cached *cur = d->cache[i]; while (cur) { struct cached *next = cur->next; cur->next = NULL; _free_cached(cur); cur = next; } } for (size_t i = 0; i< SPRIME; i++) { struct mdns_record *cur = d->published[i]; struct query *curq; while (cur) { struct mdns_record *next = cur->next; cur->next = NULL; _free_record(cur); cur = next; } curq = d->queries[i]; while (curq) { struct query *next = curq->next; curq->next = NULL; free(curq->name); free(curq); curq = next; } } u = d->uanswers; while (u) { struct unicast *next = u->next; u->next = NULL; free(u); u = next; } free(d); } void mdnsd_register_receive_callback(mdns_daemon_t *d, mdnsd_record_received_callback cb, void* data) { d->received_callback = cb; d->received_callback_data = data; } int mdnsd_in(mdns_daemon_t *d, struct message *m, struct in_addr ip, unsigned short port) { mdns_record_t *r = NULL; int i, j; if (d->shutdown) return 1; gettimeofday(&d->now, 0); if (m->header.qr == 0) { /* Process each query */ for (i = 0; i < m->qdcount; i++) { mdns_record_t *r_start, *r_next; bool has_conflict = false; if (!m->qd || m->qd[i].class != d->class) continue; INFO("Query for %s of type %d ...", m->qd[i].name, m->qd[i].type); r = _r_next(d, NULL, m->qd[i].name, m->qd[i].type); if (!r) continue; /* Service enumeratio/discovery prepeare to send all matching records */ if (!strcmp(m->qd[i].name, DISCO_NAME)) { d->disco = 1; while (r) { if (!strcmp(r->rr.name, DISCO_NAME)) _r_send(d, r); r = _r_next(d, r, m->qd[i].name, m->qd[i].type); } continue; } /* Check all of our potential answers */ for (r_start = r; r != NULL; r = r_next) { INFO("Local record: %s, type: %d, rdname: %s", r->rr.name, r->rr.type, r->rr.rdname); /* Fetch next here, because _conflict() might delete r, invalidating next */ r_next = _r_next(d, r, m->qd[i].name, m->qd[i].type); /* probing state, check for conflicts */ if (r->unique && r->unique < 5 && !r->modified) { /* Check all to-be answers against our own */ for (j = 0; j < m->ancount; j++) { if (!m->an || m->qd[i].type != m->an[j].type || strcmp(m->qd[i].name, m->an[j].name)) continue; /* This answer isn't ours, conflict! */ if (!_a_match(&m->an[j], &r->rr)) { _conflict(d, r); has_conflict = true; break; } } continue; } /* Check the known answers for this question */ for (j = 0; j < m->ancount; j++) { if (!m->an || m->qd[i].type != m->an[j].type || strcmp(m->qd[i].name, m->an[j].name)) continue; if (d->received_callback) d->received_callback(&m->an[j], d->received_callback_data); /* Do they already have this answer? */ if (_a_match(&m->an[j], &r->rr)) break; } INFO("Should we send answer? j: %d, m->ancount: %d", j, m->ancount); if (j == m->ancount) { INFO("Yes we should, enquing %s for outbound", r->rr.name); _r_send(d, r); } } /* Send the matching unicast reply */ if (!has_conflict && port != 5353) _u_push(d, r_start, m->id, ip, port); } return 0; } /* Process each answer, check for a conflict, and cache */ for (i = 0; i < m->ancount; i++) { if (!m->an) continue; if (!m->an[i].name) { ERR("Got answer with NULL name at %p. Type: %d, TTL: %ld, skipping", (void*)&m->an[i], m->an[i].type, m->an[i].ttl); continue; } INFO("Got Answer: Name: %s, Type: %d", m->an[i].name, m->an[i].type); r = _r_next(d, NULL, m->an[i].name, m->an[i].type); if (r && r->unique && r->modified && _a_match(&m->an[i], &r->rr)) { /* double check, is this actually from us, looped back? */ if (ip.s_addr == d->addr.s_addr) continue; _conflict(d, r); } if (d->received_callback) d->received_callback(&m->an[i], d->received_callback_data); if (_cache(d, &m->an[i], ip) != 0) { ERR("Failed caching answer, possibly too long packet, skipping."); continue; } } return 0; } int mdnsd_out(mdns_daemon_t *d, struct message *m, struct in_addr *ip, unsigned short *port) { mdns_record_t *r; int ret = 0; gettimeofday(&d->now, 0); memset(m, 0, sizeof(struct message)); /* Defaults, multicast */ *port = htons(5353); ip->s_addr = inet_addr("224.0.0.251"); m->header.qr = 1; m->header.aa = 1; /* Send out individual unicast answers */ if (d->uanswers) { struct unicast *u = d->uanswers; INFO("Send Unicast Answer: Name: %s, Type: %d", u->r->rr.name, u->r->rr.type); d->uanswers = u->next; *port = htons(u->port); *ip = u->to; m->id = u->id; message_qd(m, u->r->rr.name, u->r->rr.type, d->class); message_an(m, u->r->rr.name, u->r->rr.type, d->class, u->r->rr.ttl); u->r->last_sent = d->now; _a_copy(m, &u->r->rr); free(u); return 1; } /* Accumulate any immediate responses */ if (d->a_now) ret += _r_out(d, m, &d->a_now); /* Check if it's time to send the publish retries (unlink if done) */ if (!d->probing && d->a_publish && _tvdiff(d->now, d->publish) <= 0) { mdns_record_t *cur = d->a_publish; mdns_record_t *last = NULL; mdns_record_t *next; while (cur && message_packet_len(m) + (int)_rr_len(&cur->rr) < d->frame) { if (cur->rr.type == QTYPE_PTR) { INFO("Send Publish PTR: Name: %s, rdlen: %d, rdata: %s, rdname: %s", cur->rr.name,cur->rr.rdlen, cur->rr.rdata, cur->rr.rdname); } else if (cur->rr.type == QTYPE_SRV) { INFO("Send Publish SRV: Name: %s, rdlen: %d, rdata: %s, rdname: %s, port: %d, prio: %d, weight: %d", cur->rr.name,cur->rr.rdlen, cur->rr.rdname, cur->rr.rdata, cur->rr.srv.port, cur->rr.srv.priority, cur->rr.srv.weight); } else { INFO("Send Publish: Name: %s, Type: %d, rdname: %s", cur->rr.name, cur->rr.type, cur->rr.rdname); } next = cur->list; ret++; cur->tries++; if (cur->unique) message_an(m, cur->rr.name, cur->rr.type, d->class + 32768, cur->rr.ttl); else message_an(m, cur->rr.name, cur->rr.type, d->class, cur->rr.ttl); _a_copy(m, &cur->rr); cur->last_sent = d->now; if (cur->rr.ttl != 0 && cur->tries < 4) { last = cur; cur = next; continue; } cur->list = NULL; if (d->a_publish == cur) d->a_publish = next; if (last) last->list = next; if (cur->rr.ttl == 0) _r_done(d, cur); cur = next; } if (d->a_publish) { d->publish.tv_sec = d->now.tv_sec + 2; d->publish.tv_usec = d->now.tv_usec; } } /* If we're in shutdown, we're done */ if (d->shutdown) return ret; /* Check if a_pause is ready */ if (d->a_pause && _tvdiff(d->now, d->pause) <= 0) ret += _r_out(d, m, &d->a_pause); /* Now process questions */ if (ret) return ret; m->header.qr = 0; m->header.aa = 0; if (d->probing && _tvdiff(d->now, d->probe) <= 0) { mdns_record_t *last = 0; /* Scan probe list to ask questions and process published */ for (r = d->probing; r != NULL;) { /* Done probing, publish */ if (r->unique == 4) { mdns_record_t *next = r->list; if (d->probing == r) d->probing = r->list; else if (last) last->list = r->list; r->list = 0; r->unique = 5; _r_publish(d, r); r = next; continue; } INFO("Send Probing: Name: %s, Type: %d", r->rr.name, r->rr.type); message_qd(m, r->rr.name, r->rr.type, (unsigned short)d->class); r->last_sent = d->now; last = r; r = r->list; } /* Scan probe list again to append our to-be answers */ for (r = d->probing; r != 0; r = r->list) { r->unique++; INFO("Send Answer in Probe: Name: %s, Type: %d", r->rr.name, r->rr.type); message_ns(m, r->rr.name, r->rr.type, (unsigned short)d->class, r->rr.ttl); _a_copy(m, &r->rr); r->last_sent = d->now; ret++; } /* Process probes again in the future */ if (ret) { d->probe.tv_sec = d->now.tv_sec; d->probe.tv_usec = d->now.tv_usec + 250000; return ret; } } /* Process qlist for retries or expirations */ if (d->checkqlist && (unsigned long)d->now.tv_sec >= d->checkqlist) { struct query *q; struct cached *c; unsigned long int nextbest = 0; /* Ask questions first, track nextbest time */ for (q = d->qlist; q != 0; q = q->list) { if (q->nexttry > 0 && q->nexttry <= (unsigned long)d->now.tv_sec && q->tries < 3) message_qd(m, q->name, q->type, d->class); else if (q->nexttry > 0 && (nextbest == 0 || q->nexttry < nextbest)) nextbest = q->nexttry; } /* Include known answers, update questions */ for (q = d->qlist; q != 0; q = q->list) { if (q->nexttry == 0 || q->nexttry > (unsigned long)d->now.tv_sec) continue; /* Done retrying, expire and reset */ if (q->tries == 3) { _c_expire(d, &d->cache[_namehash(q->name) % LPRIME]); _q_reset(d, q); continue; } ret++; q->nexttry = d->now.tv_sec + ++q->tries; if (nextbest == 0 || q->nexttry < nextbest) nextbest = q->nexttry; /* If room, add all known good entries */ c = 0; while ((c = _c_next(d, c, q->name, q->type)) != 0 && c->rr.ttl > (unsigned long)d->now.tv_sec + 8 && message_packet_len(m) + (int)_rr_len(&c->rr) < d->frame) { INFO("Add known answer: Name: %s, Type: %d", c->rr.name, c->rr.type); message_an(m, q->name, (unsigned short)q->type, (unsigned short)d->class, c->rr.ttl - (unsigned long)d->now.tv_sec); _a_copy(m, &c->rr); } } d->checkqlist = nextbest; } if ((unsigned long)d->now.tv_sec > d->expireall) _gc(d); return ret; } #define RET do { \ while (d->sleep.tv_usec > 1000000) { \ d->sleep.tv_sec++; \ d->sleep.tv_usec -= 1000000; \ } \ return &d->sleep; \ } while (0) struct timeval *mdnsd_sleep(mdns_daemon_t *d) { time_t expire; long usec; d->sleep.tv_sec = d->sleep.tv_usec = 0; /* First check for any immediate items to handle */ if (d->uanswers || d->a_now) return &d->sleep; gettimeofday(&d->now, 0); /* Then check for paused answers or nearly expired records */ if (d->a_pause) { if ((usec = _tvdiff(d->now, d->pause)) > 0) d->sleep.tv_usec = usec; RET; } /* Now check for probe retries */ if (d->probing) { if ((usec = _tvdiff(d->now, d->probe)) > 0) d->sleep.tv_usec = usec; RET; } /* Now check for publish retries */ if (d->a_publish) { if ((usec = _tvdiff(d->now, d->publish)) > 0) d->sleep.tv_usec = usec; RET; } /* Also check for queries with known answer expiration/retry */ if (d->checkqlist) { long sec; if ((sec = (long)d->checkqlist - d->now.tv_sec) > 0) d->sleep.tv_sec = sec; RET; } /* Resend published records before TTL expires */ expire = (long)d->expireall - d->now.tv_sec; if (expire < 0) RET; for (size_t i = 0; i < SPRIME; i++) { mdns_record_t *r; time_t next; r = d->published[i]; if (!r) continue; /* Publish 2 seconds before expiration */ next = r->last_sent.tv_sec + (long)r->rr.ttl - d->now.tv_sec; if (next <= 2) { INFO("Republish %s before TTL expires ...", r->rr.name); _r_push(&d->a_pause, r); } if (next < expire) expire = next; } d->sleep.tv_sec = expire > 2 ? expire - 2 : 0; d->pause.tv_sec = d->now.tv_sec + d->sleep.tv_sec; RET; } void mdnsd_query(mdns_daemon_t *d, const char *host, int type, int (*answer)(mdns_answer_t *a, void *arg), void *arg) { struct query *q; struct cached *cur = 0; int i = _namehash(host) % SPRIME; if (!(q = _q_next(d, 0, host, type))) { if (!answer) return; q = calloc(1, sizeof(struct query)); if (!q) return; q->name = strdup(host); if (!q->name) { free(q); return; } q->type = type; q->next = d->queries[i]; q->list = d->qlist; d->qlist = d->queries[i] = q; /* Any cached entries should be associated */ while ((cur = _c_next(d, cur, q->name, q->type))) cur->q = q; _q_reset(d, q); /* New question, immediately send out */ q->nexttry = d->checkqlist = d->now.tv_sec; } /* No answer means we don't care anymore */ if (!answer) { _q_done(d, q); return; } q->answer = answer; q->arg = arg; } mdns_answer_t *mdnsd_list(mdns_daemon_t *d,const char *host, int type, mdns_answer_t *last) { return (mdns_answer_t *)_c_next(d, (struct cached *)last, host, type); } mdns_record_t *mdnsd_record_next(const mdns_record_t* r) { return r ? r->next : NULL; } const mdns_answer_t *mdnsd_record_data(const mdns_record_t* r) { return &r->rr; } mdns_record_t *mdnsd_shared(mdns_daemon_t *d, const char *host, unsigned short type, unsigned long ttl) { int i = _namehash(host) % SPRIME; mdns_record_t *r; r = calloc(1, sizeof(struct mdns_record)); if (!r) return NULL; r->rr.name = strdup(host); if (!r->rr.name) { free(r); return NULL; } r->rr.type = type; r->rr.ttl = ttl; r->next = d->published[i]; d->published[i] = r; return r; } mdns_record_t *mdnsd_unique(mdns_daemon_t *d, const char *host, unsigned short type, unsigned long ttl, void (*conflict)(char *host, int type, void *arg), void *arg) { mdns_record_t *r; r = mdnsd_shared(d, host, type, ttl); if (!r) return NULL; r->conflict = conflict; r->arg = arg; r->unique = 1; /* check if r already in other lists. If yes, remove it from there */ _r_remove_lists(d, r, &d->probing); _r_push(&d->probing, r); d->probe.tv_sec = d->now.tv_sec; d->probe.tv_usec = d->now.tv_usec; return r; } mdns_record_t *mdnsd_get_published(mdns_daemon_t *d, const char *host) { return d->published[_namehash(host) % SPRIME]; } int mdnsd_has_query(mdns_daemon_t *d, const char *host) { return d->queries[_namehash(host) % SPRIME] != NULL; } mdns_record_t *mdnsd_find(mdns_daemon_t *d, const char *name, unsigned short type) { mdns_record_t *r; r = mdnsd_get_published(d, name); while (r) { const mdns_answer_t *data; /* * Search for a record with the same type and name. * Records with different names might be in the same * linked list when the hash functions % SPRIME assigns * them the same index (hash collision) */ data = mdnsd_record_data(r); if (data->type == type && strcmp(data->name, name) == 0) return r; r = mdnsd_record_next(r); } return NULL; } void mdnsd_done(mdns_daemon_t *d, mdns_record_t *r) { mdns_record_t *cur; if (r->unique && r->unique < 5) { /* Probing yet, zap from that list first! */ if (d->probing == r) { d->probing = r->list; } else { for (cur = d->probing; cur->list != r; cur = cur->list) ; cur->list = r->list; } _r_done(d, r); return; } r->rr.ttl = 0; _r_send(d, r); } void mdnsd_set_raw(mdns_daemon_t *d, mdns_record_t *r, const char *data, unsigned short len) { if (r->rr.rdata) free(r->rr.rdata); r->rr.rdata = malloc(len); if (r->rr.rdata) { memcpy(r->rr.rdata, data, len); r->rr.rdlen = len; } _r_publish(d, r); } void mdnsd_set_host(mdns_daemon_t *d, mdns_record_t *r, const char *name) { if (!r) return; if (r->rr.rdname) free(r->rr.rdname); r->rr.rdname = strdup(name); _r_publish(d, r); } void mdnsd_set_ip(mdns_daemon_t *d, mdns_record_t *r, struct in_addr ip) { r->rr.ip = ip; _r_publish(d, r); } void mdnsd_set_ipv6(mdns_daemon_t *d, mdns_record_t *r, struct in6_addr ip6) { r->rr.ip6 = ip6; _r_publish(d, r); } void mdnsd_set_srv(mdns_daemon_t *d, mdns_record_t *r, unsigned short priority, unsigned short weight, unsigned short port, char *name) { r->rr.srv.priority = priority; r->rr.srv.weight = weight; r->rr.srv.port = port; mdnsd_set_host(d, r, name); } static int process_in(mdns_daemon_t *d, int sd) { static unsigned char buf[MAX_PACKET_LEN + 1]; struct sockaddr_in from; socklen_t ssize = sizeof(struct sockaddr_in); ssize_t bsize; memset(buf, 0, sizeof(buf)); while ((bsize = recvfrom(sd, buf, MAX_PACKET_LEN, MSG_DONTWAIT, (struct sockaddr *)&from, &ssize)) > 0) { struct message m = { 0 }; int rc; buf[MAX_PACKET_LEN] = 0; mdnsd_log_hex("Got Data:", buf, bsize); rc = message_parse(&m, buf); if (rc) continue; rc = mdnsd_in(d, &m, from.sin_addr, ntohs(from.sin_port)); if (rc) continue; } if (bsize < 0 && errno != EAGAIN) return 1; return 0; } static int process_out(mdns_daemon_t *d, int sd) { unsigned short int port; struct sockaddr_in to; struct in_addr ip; struct message m; while (mdnsd_out(d, &m, &ip, &port)) { unsigned char *buf; ssize_t len; memset(&to, 0, sizeof(to)); to.sin_family = AF_INET; to.sin_port = port; to.sin_addr = ip; len = message_packet_len(&m); buf = message_packet(&m); mdnsd_log_hex("Send Data:", buf, len); if (sendto(sd, buf, len, MSG_DONTWAIT, (struct sockaddr *)&to, sizeof(struct sockaddr_in)) != len) return 2; } return 0; } int mdnsd_step(mdns_daemon_t *d, int sd, bool in, bool out, struct timeval *tv) { int rc = 0; if (in) rc = process_in(d, sd); if (!rc && out) rc = process_out(d, sd); if (!rc && tv) { struct timeval *delay; delay = mdnsd_sleep(d); memcpy(tv, delay, sizeof(*tv)); } /* Service Enumeration/Discovery completed */ if (d && d->disco) d->disco = 0; return rc; } void records_clear(mdns_daemon_t *d) { for (int i = 0; i < SPRIME; i++) { mdns_record_t *r = d->published[i]; while (r) { mdns_record_t *const next = r->next; _r_remove_lists(d, r, NULL); r = next; } d->published[i] = NULL; } } mdnsd-0.12/libmdnsd/mdnsd.h000066400000000000000000000204641436331001000156100ustar00rootroot00000000000000/* * Copyright (c) 2003 Jeremie Miller * Copyright (c) 2016-2022 Joachim Wiberg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef LIB_MDNSD_H_ #define LIB_MDNSD_H_ #include "config.h" #include "1035.h" #include #include #include #include #define QCLASS_IN (1) #define DISCO_NAME "_services._dns-sd._udp.local." #define DBG(fmt, args...) mdnsd_log(LOG_DEBUG, "%s(): " fmt, __func__, ##args) #define INFO(fmt, args...) mdnsd_log(LOG_INFO, "%s(): " fmt, __func__, ##args) #define NOTE(fmt, args...) mdnsd_log(LOG_NOTICE, fmt, ##args) #define WARN(fmt, args...) mdnsd_log(LOG_WARNING, fmt, ##args) #define ERR(fmt, args...) mdnsd_log(LOG_ERR, fmt, ##args) /* Main daemon data */ typedef struct mdns_daemon mdns_daemon_t; /* Record entry */ typedef struct mdns_record mdns_record_t; /* Callback for received record. Data is passed from the register call */ typedef void (*mdnsd_record_received_callback)(const struct resource* r, void* data); /* Answer data */ typedef struct mdns_answer { char *name; unsigned short int type; unsigned long int ttl; unsigned short int rdlen; unsigned char *rdata; struct in_addr ip; /* A, network byte order */ struct in6_addr ip6; /* AAAA, network byte order */ char *rdname; /* NS/CNAME/PTR/SRV */ struct { unsigned short int priority, weight, port; } srv; /* SRV */ } mdns_answer_t; /** * Global functions */ /** * Enable logging to syslog, stdout/stderr is used by default */ void mdnsd_log_open(const char *ident); /** * Adjust log level, by default LOG_NOTICE */ int mdnsd_log_level(char *level); /** * Log current time to DBG() or buf */ void mdnsd_log_time(struct timeval *tv, char *buf, size_t len); /** * HEX dump a buffer to log */ void mdnsd_log_hex(char *msg, unsigned char *buffer, ssize_t len); /** * Log to syslog or stdio */ void mdnsd_log(int severity, const char *fmt, ...); /** * Create a new mdns daemon for the given class of names (usually 1) and * maximum frame size */ mdns_daemon_t *mdnsd_new(int class, int frame); /** * Set mDNS daemon host IP address */ void mdnsd_set_address(mdns_daemon_t *d, struct in_addr addr); /** * Get mDNS daemon host IP address from previous set */ struct in_addr mdnsd_get_address(mdns_daemon_t *d); /** * Set mDNS daemon host IPv6 address */ void mdnsd_set_ipv6_address(mdns_daemon_t *d, struct in6_addr addr); /** * Get mDNS daemon host IPv6 address from previous set */ struct in6_addr mdnsd_get_ipv6_address(mdns_daemon_t *d); /** * Gracefully shutdown the daemon, use mdnsd_out() to get the last * packets */ void mdnsd_shutdown(mdns_daemon_t *d); /** * Flush all cached records (network/interface changed) */ void mdnsd_flush(mdns_daemon_t *d); /** * Free given mdns_daemon_t *(should have used mdnsd_shutdown() first!) */ void mdnsd_free(mdns_daemon_t *d); /** * Register callback which is called when a record is received. The data parameter is passed to the callback. * Calling this multiple times overwrites the previous register. */ void mdnsd_register_receive_callback(mdns_daemon_t *d, mdnsd_record_received_callback cb, void *data); /** * I/O functions */ /** * Oncoming message from host (to be cached/processed) */ int mdnsd_in(mdns_daemon_t *d, struct message *m, struct in_addr ip, unsigned short port); /** * Outgoing messge to be delivered to host, returns >0 if one was * returned and m/ip/port set */ int mdnsd_out(mdns_daemon_t *d, struct message *m, struct in_addr *ip, unsigned short *port); /** * returns the max wait-time until mdnsd_out() needs to be called again */ struct timeval *mdnsd_sleep(mdns_daemon_t *d); /** * Q/A functions */ /** * Register a new query * * The answer() callback is called whenever one is found/changes/expires * (immediate or anytime after, mdns_answer_t valid until ->ttl==0) * either answer returns -1, or another mdnsd_query() with a %NULL answer * will remove/unregister this query */ void mdnsd_query(mdns_daemon_t *d, const char *host, int type, int (*answer)(mdns_answer_t *a, void *arg), void *arg); /** * Returns the first (if last == NULL) or next answer after last from * the cache mdns_answer_t only valid until an I/O function is called */ mdns_answer_t *mdnsd_list(mdns_daemon_t *d, const char *host, int type, mdns_answer_t *last); /** * Returns the next record of the given record, i.e. the value of next field. * @param r the base record * @return r->next */ mdns_record_t *mdnsd_record_next(const mdns_record_t *r); /** * Gets the record data */ const mdns_answer_t *mdnsd_record_data(const mdns_record_t *r); /** * Publishing functions */ /** * Create a new unique record * * Call mdnsd_list() first to make sure the record is not used yet. * * The conflict() callback is called at any point when one is detected * and unable to recover after the first data is set_*(), any future * changes effectively expire the old one and attempt to create a new * unique record */ mdns_record_t *mdnsd_unique(mdns_daemon_t *d, const char *host, unsigned short type, unsigned long ttl, void (*conflict)(char *host, int type, void *arg), void *arg); /** * Create a new shared record */ mdns_record_t *mdnsd_shared(mdns_daemon_t *d, const char *host, unsigned short type, unsigned long ttl); /** * Get a previously created record based on the host name. NULL if not found. Does not return records for other hosts. * If multiple records are found, use record->next to iterate over all the results. */ mdns_record_t *mdnsd_get_published(mdns_daemon_t *d, const char *host); /** * Check if there is already a query for the given host */ int mdnsd_has_query(mdns_daemon_t *d, const char *host); /** * Find previously based record based on name and type */ mdns_record_t *mdnsd_find(mdns_daemon_t *d, const char *name, unsigned short type); /** * de-list the given record */ void mdnsd_done(mdns_daemon_t *d, mdns_record_t *r); /** * These all set/update the data for the given record, nothing is * published until they are called */ void mdnsd_set_raw(mdns_daemon_t *d, mdns_record_t *r, const char *data, unsigned short len); void mdnsd_set_host(mdns_daemon_t *d, mdns_record_t *r, const char *name); void mdnsd_set_ip(mdns_daemon_t *d, mdns_record_t *r, struct in_addr ip); void mdnsd_set_ipv6(mdns_daemon_t *d, mdns_record_t *r, struct in6_addr ip6); void mdnsd_set_srv(mdns_daemon_t *d, mdns_record_t *r, unsigned short priority, unsigned short weight, unsigned short port, char *name); /** * Process input queue and output queue. Should be called at least the time which is returned in nextSleep. * Returns 0 on success, 1 on read error, 2 on write error */ int mdnsd_step(mdns_daemon_t *d, int mdns_socket, bool processIn, bool processOut, struct timeval *tv); /** * Clear all records from the list published * Returns none */ void records_clear(mdns_daemon_t *d); #endif /* LIB_MDNSD_H_ */ mdnsd-0.12/libmdnsd/sdtxt.c000066400000000000000000000064101436331001000156370ustar00rootroot00000000000000/* Service discovery TXT record parsing/generation * * Copyright (c) 2003 Jeremie Miller * Copyright (c) 2016-2022 Joachim Wiberg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "sdtxt.h" #include #include static size_t _sd2txt_len(const char *key, char *val) { size_t ret = strlen(key); if (!*val) return ret; ret += strlen(val); ret++; return ret; } static void _sd2txt_count(xht_t *__attribute__((unused)) h, const char *key, void *val, void *arg) { int *const count = arg; *count += (int)_sd2txt_len(key, val) + 1; } static void _sd2txt_write(xht_t *__attribute__((unused)) h, const char *key, void *val, void *arg) { unsigned char **txtp = arg; char *const cval = val; /* Copy in lengths, then strings */ **txtp = _sd2txt_len(key, val); (*txtp)++; memcpy(*txtp, key, strlen(key)); *txtp += strlen(key); if (!*cval) return; **txtp = '='; (*txtp)++; memcpy(*txtp, cval, strlen(cval)); *txtp += strlen(cval); } unsigned char *sd2txt(xht_t *h, int *len) { unsigned char *buf, *raw; *len = 0; xht_walk(h, _sd2txt_count, len); if (!*len) { *len = 1; return (unsigned char *)strdup(""); } raw = buf = malloc(*len); if (!buf) return NULL; xht_walk(h, _sd2txt_write, &buf); return raw; } xht_t *txt2sd(unsigned char *txt, int len) { xht_t *h = 0; if (txt == 0 || len == 0 || *txt == 0) return 0; h = xht_new(23); /* Loop through data breaking out each block, storing into hashtable */ for (; *txt <= len && len > 0; len -= *txt, txt += *txt + 1) { char key[256], *val; if (*txt == 0) break; memcpy(key, txt + 1, *txt); key[*txt] = 0; val = strchr(key, '='); if (val) { *val++ = 0; xht_store(h, key, (int)strlen(key), val, (int)strlen(val)); } } return h; } mdnsd-0.12/libmdnsd/sdtxt.h000066400000000000000000000040221436331001000156410ustar00rootroot00000000000000/* Service discovery TXT record parsing/generation * * Copyright (c) 2003 Jeremie Miller * Copyright (c) 2016-2022 Joachim Wiberg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef MDNS_SDTXT_H_ #define MDNS_SDTXT_H_ #include "xht.h" /** * returns hashtable of strings from the SD TXT record rdata */ xht_t *txt2sd(unsigned char *txt, int len); /** * returns a raw block that can be sent with a SD TXT record, sets length */ unsigned char *sd2txt(xht_t *h, int *len); #endif /* MDNS_SDTXT_H_ */ mdnsd-0.12/libmdnsd/xht.c000066400000000000000000000124501436331001000152750ustar00rootroot00000000000000/* Simple string->void* hashtable, very static and bare minimal, but efficient * * Copyright (c) 2003 Jeremie Miller * Copyright (c) 2016-2022 Joachim Wiberg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "xht.h" #include #include typedef struct xhn { char flag; struct xhn *next; union { char *key; const char *ckey; } u; void *val; } xhn_t; struct xht { int prime; xhn_t *zen; }; /* Generates a hash code for a string. * This function uses the ELF hashing algorithm as reprinted in * Andrew Binstock, "Hashing Rehashed," Dr. Dobb's Journal, April 1996. */ static int _xhter(const char *s) { /* ELF hash uses unsigned chars and unsigned arithmetic for portability */ const unsigned char *name = (const unsigned char *)s; unsigned long h = 0, g; while (*name) { /* do some fancy bitwanking on the string */ h = (h << 4) + (unsigned long)(*name++); if ((g = (h & 0xF0000000UL)) != 0) h ^= (g >> 24); h &= ~g; } return (int)h; } static xhn_t *_xht_node_find(xhn_t *n, const char *key) { for (; n != 0; n = n->next) if (n->u.ckey && strcmp(key, n->u.ckey) == 0) return n; return 0; } xht_t *xht_new(int prime) { xht_t *xnew; xnew = malloc(sizeof(struct xht)); if (!xnew) return NULL; xnew->prime = prime; xnew->zen = calloc(1, sizeof(struct xhn) * prime); /* array of xhn_t size of prime */ if (!xnew->zen) { free(xnew); return NULL; } return xnew; } /* does the set work, used by xht_set and xht_store */ static xhn_t *_xht_set(xht_t *h, const char *key, void *val, char flag) { int i; xhn_t *n; /* get our index for this key */ i = _xhter(key) % h->prime; /* check for existing key first, or find an empty one */ n = _xht_node_find(&h->zen[i], key); if (n == NULL) { for (n = &h->zen[i]; n != 0; n = n->next) { if (n->val == NULL) break; } } /* if none, make a new one, link into this index */ if (n == NULL) { n = calloc(1, sizeof(struct xhn)); if (n == NULL) return NULL; n->next = h->zen[i].next; h->zen[i].next = n; } /* When flag is set, we manage their mem and free em first */ if (n->flag) { free(n->u.key); free(n->val); } n->flag = flag; n->u.ckey = key; n->val = val; return n; } void xht_set(xht_t *h, const char *key, void *val) { if (h == NULL || h->zen == NULL || key == NULL) return; _xht_set(h, key, val, 0); } void xht_store(xht_t *h, const char *key, int klen, void *val, int vlen) { char *ckey, *cval; if (h == NULL || h->zen == NULL || key == NULL || klen == 0 || val == NULL) return; ckey = malloc(klen + 1); if (!ckey) return; memcpy(ckey, key, klen); ckey[klen] = '\0'; cval = malloc(vlen + 1); if (!cval) { free(ckey); return; } memcpy(cval, val, vlen); cval[vlen] = '\0'; /* convenience, in case it was a string too */ _xht_set(h, ckey, cval, 1); } void *xht_get(xht_t *h, const char *key) { xhn_t *n; if (h == NULL || h->zen == NULL || key == NULL) return NULL; n = _xht_node_find(&h->zen[_xhter(key) % h->prime], key); if (n == NULL) return NULL; return n->val; } void xht_free(xht_t *h) { int i; if (h == NULL) return; for (i = 0; i < h->prime; i++) { xhn_t *n = &h->zen[i]; if (n->flag) { free(n->u.key); n->u.key = NULL; free(n->val); n->val = NULL; } for (n = (&h->zen[i])->next; n != 0;) { xhn_t *f = n->next; n->next = NULL; if (n->flag) { free(n->u.key); n->u.key = NULL; free(n->val); n->val = NULL; } free(n); n = f; } } free(h->zen); h->zen = NULL; free(h); } void xht_walk(xht_t *h, xht_walker w, void *arg) { int i; xhn_t *n; if (h == NULL || h->zen == NULL || w == NULL) return; for (i = 0; i < h->prime; i++) { for (n = &h->zen[i]; n != 0; n = n->next) { if (n->u.ckey != 0 && n->val != 0) (*w)(h, n->u.ckey, n->val, arg); } } } mdnsd-0.12/libmdnsd/xht.h000066400000000000000000000053161436331001000153050ustar00rootroot00000000000000/* Simple string->void* hashtable, very static and bare minimal, but efficient * * Copyright (c) 2003 Jeremie Miller * Copyright (c) 2016-2022 Joachim Wiberg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef MDNS_XHT_H_ #define MDNS_XHT_H_ typedef struct xht xht_t; /** * must pass a prime# */ xht_t *xht_new(int prime); /** * caller responsible for key storage, no copies made * * set val to NULL to clear an entry, memory is reused but never free'd * (# of keys only grows to peak usage) * * Note: don't free it b4 xht_free()! */ void xht_set(xht_t *h, const char *key, void *val); /** * Unlike xht_set() where key/val is in caller's mem, here they are * copied into xht and free'd when val is 0 or xht_free() */ void xht_store(xht_t *h, const char *key, int klen, void *val, int vlen); /** * returns value of val if found, or NULL */ void *xht_get(xht_t *h, const char *key); /** * free the hashtable and all entries */ void xht_free(xht_t *h); /** * pass a function that is called for every key that has a value set */ typedef void (*xht_walker)(xht_t *h, const char *key, void *val, void *arg); void xht_walk(xht_t *h, xht_walker w, void *arg); #endif /* MDNS_XHT_H_ */ mdnsd-0.12/m4/000077500000000000000000000000001436331001000130505ustar00rootroot00000000000000mdnsd-0.12/m4/.gitignore000066400000000000000000000000051436331001000150330ustar00rootroot00000000000000*.m4 mdnsd-0.12/man/000077500000000000000000000000001436331001000133035ustar00rootroot00000000000000mdnsd-0.12/man/Makefile.am000066400000000000000000000001241436331001000153340ustar00rootroot00000000000000dist_man1_MANS = mquery.1 dist_man5_MANS = mdnsd.service.5 dist_man8_MANS = mdnsd.8 mdnsd-0.12/man/mdnsd.8000066400000000000000000000066731436331001000145150ustar00rootroot00000000000000.\" Copyright (c) 2021-2022 Joachim Wiberg .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions are met: .\" * Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" * Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" * Neither the name of the copyright holders nor the names of its .\" contributors may be used to endorse or promote products derived from .\" this software without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED .\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY .\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES .\" (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; .\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON .\" ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS .\" SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" .Dd Jan 4 2022 .Dt MDNSD 8 SMM .Os .Sh NAME .Nm mdnsd .Nd small multicast DNS daemon .Sh SYNOPSIS .Nm mdnsd .Op Fl hnsv .Op Fl i Ar IFACE .Op Fl l Ar LEVEL .Op Fl t Ar TTL .Op Ar PATH .Sh DESCRIPTION .Nm is a small mDNS-SD daemon for UNIX like systems. It is based on an original implementation made by Jeremie Miller in 2003. Multicast DNS with service discovery (-SD) is an open standard defined in RFC5762, mDNS-SD in RFC6763, commonly known to end-users as Bonjour or Avahi, depending on the operating system. .Pp .Nm reads service definitions of services to announce from .Pa /etc/mdns.d/*.service , a different path may be given on the command line, which can be a directory or a single service file. .Pp .Nm by default runs on all multicast capable interfaces on a system. Use .Fl i Ar IFACE to only run on a single interface. .Sh OPTIONS This program follows the usual UNIX command line syntax. The options are as follows: .Bl -tag .It Fl h Print a help message and exit. .It Fl i Ar IFACE Interface to announce services on. By default .Nm runs on all interfaces. .Pp Only available on Linux. .It Fl l Ar LEVEL Set log level: none, err, notice (default), info, debug. .It Fl n Run in foreground, do not detach from controlling terminal. .It Fl s Use syslog even if running in foreground. .It Fl t Ar TTL Set TTL of mDNS packets, default: 1 (link-local only). .It Fl v Show program version. .El .Sh FILES .Bl -tag -width /etc/mdns.d/*.service -compact .It Pa /etc/mdns.d/*.service mDNS-SD services to announce. .El .Sh SEE ALSO .Xr mquery 1 , .Xr mdnsd.service 5 , .Xr mdns-scan 1 .Sh AUTHORS This mDNS-SD implementation was created in 2003 by .An Jeremie Miller Aq jer@jabber.org . Much later, in 2016, it was adopted by .An Joachim Wiberg Aq troglobit@gmail.com for further development and maintenance at GitHub: .Aq https://github.com/troglobit/mdnsd . mdnsd-0.12/man/mdnsd.service.5000066400000000000000000000104711436331001000161400ustar00rootroot00000000000000.\" Copyright (c) 2021-2022 Joachim Wiberg .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions are met: .\" * Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" * Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" * Neither the name of the copyright holders nor the names of its .\" contributors may be used to endorse or promote products derived from .\" this software without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED .\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY .\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES .\" (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; .\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON .\" ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS .\" SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" .Dd Jan 4 2022 .Dt MDNSD.SERVICE 5 SMM .Os .Sh NAME .Nm mdnsd.service .Nd mdnsd service configuration file .Sh SYNOPSIS .Nm /etc/mdns.d/*.service .Sh DESCRIPTION .Nm mdnsd needs at least one .Pa .service file to actively announce itself. The systems that .Nm mdnsd targets usually have an SSH or HTTP/HTTPS service, which can be located by end-users with mDNS-SD. E.g., .Nm mdnsd may be embedded in your printer, an access point, an industrial switch, or other headless network connected device. .Pp Services are defined one per .Pa .service file and only two fields are mandatory: .Bl -tag -width TERM .It Cm type Ar TYPE The .Ar TYPE can be any service type from .Pa /etc/services , prefixed with an underscore. E.g., 'ipp' is port 631/tcp, which gives the type .Cm _ipp._tcp .It Cm port Ar PORT The port is the UDP or TCP port to announce. This can also be found in the file .Pa /etc/services . .El .Pp There are also a few optional fields supported, see below for examples: .Bl -tag -width TERM .It Cm name Ar STRING Free form string to describe service. Usually what is presented to end users by discovery agents. .It Cm txt Ar STRING Text records are free-form strings that are included in service announcements. Useful for announcing meta data about your service. .It Cm target Ar URL URL to announce as canonical for this service. .It Cm cname Ar URL A .Cm cname is an alias that can be announced for unique service reqords. E.g., if only one HTTP service is available on the LAN it can be reached through the full name, or using the .Cm cname alias. .El .Sh EXAMPLES This section has some examples of mDNS service definitions. .Ss SSH .Bd -unfilled -offset indent # mDNS-SD advertisement of SSH service type _ssh._tcp port 22 .Ed .Ss FTP .Bd -unfilled -offset indent # mDNS-SD advertisement of FTP service name Troglobit FTP Server type _ftp._tcp port 21 txt server=uftpd txt version=2.6 target ftp.luthien.local .Ed .Ss HTTP .Bd -unfilled -offset indent # mDNS-SD advertisement of HTTP service type _http._tcp port 80 txt server=merecat txt version=2.31 cname home.local .Ed .Ss Printer .Bd -unfilled -offset indent # mDNS-SD advertisement of foo._printer._tcp.local. type _printer._tcp port 515 .Ed .Ss IPP .Bd -unfilled -offset indent # mDNS-SD advertisement of foo._ipp._tcp.local. type _ipp._tcp port 631 .Ed .Sh FILES .Bl -tag -width /etc/services -compact .It Pa /etc/services List of standard service definitions in UNIX .El .Sh SEE ALSO .Xr mquery 1 , .Xr mdnsd 8 .Sh AUTHORS This mDNS-SD implementation was initially developed by .An Jeremie Miller Aq jer@jabber.org in 2003. Much later .An Joachim Wiberg Aq troglobit@gmail.com adopted it for further development and maintenance .Nm at .Lk https://github.com/troglobit/mdnsd GitHub mdnsd-0.12/man/mquery.1000066400000000000000000000065051436331001000147150ustar00rootroot00000000000000.\" Copyright (c) 2021-2022 Joachim Wiberg .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions are met: .\" * Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" * Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" * Neither the name of the copyright holders nor the names of its .\" contributors may be used to endorse or promote products derived from .\" this software without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED .\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY .\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES .\" (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; .\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON .\" ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS .\" SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" .Dd Jan 4 2022 .Dt MQUERY 1 .Os .Sh NAME .Nm mquery .Nd small query tool for multicast DNS .Sh SYNOPSIS .Nm mquery .Op Fl hsv .Op Fl i Ar IFACE .Op Fl t Ar TYPE .Op Fl w Ar SEC .Op Ar NAME .Sh DESCRIPTION .Nm is a companion tool to .Xr mdnsd 8 , but can also be used stand-alone with other mDNS servers. It formats and sends mDNS service queries, similar to how .Xr mdns-scan 1 works, but with a fraction of the usability. .Pp .Nm runs on the system default interface, derived from the routing table. To run on another interface, use the .Fl i Ar IFACE command line option. It runs continuously monitoring for responses to the initial query, by default .Cm _services._dns-sd._udp.local. .Pp .Sh OPTIONS This program follows the usual UNIX command line syntax. The options are as follows: .Bl -tag .It Fl h Print a bare bones help message and exits. .It Fl i Ar IFACE Interface to run query on, by default the system default interface is used, derived from the routing table. .Pp Only available on Linux. .It Fl s By default .Nm behaves a lot like .Xr mdns-scan 1 , which after receiving an initial reply to a scan, sends a specific PTR query to the respondent. This option skips that specific scan and runs .Nm in a simple mode. .It Fl t Ar TYPE Query type, default 12 (PTR). .It Fl h Print a help message and exit. .It Fl v Show program version. .It Fl w Ar SEC Delay (approximate) in seconds before automatically exiting. .El .Sh SEE ALSO .Xr mdns-scan 1 .Xr mdnsd 8 , .Sh AUTHORS This mDNS-SD implementation was created in 2003 by .An Jeremie Miller Aq jer@jabber.org . Much later, in 2016, it was adopted by .An Joachim Wiberg Aq troglobit@gmail.com for further development and maintenance at GitHub: .Aq https://github.com/troglobit/mdnsd . mdnsd-0.12/mdnsd.service.in000066400000000000000000000004431436331001000156250ustar00rootroot00000000000000[Unit] Description=embeddable multicast DNS daemon Documentation=https://github.com/troglobit/mdnsd ConditionPathExists=@SYSCONFDIR@/mdns.d/ After=network-online.target Requires=network-online.target [Service] Type=simple ExecStart=@SBINDIR@/mdnsd -sn [Install] WantedBy=multi-user.target mdnsd-0.12/src/000077500000000000000000000000001436331001000133175ustar00rootroot00000000000000mdnsd-0.12/src/.gitignore000066400000000000000000000000651436331001000153100ustar00rootroot00000000000000*~ *.o .deps .libs Makefile Makefile.in mdnsd mquery mdnsd-0.12/src/Makefile.am000066400000000000000000000011631436331001000153540ustar00rootroot00000000000000AUTOMAKE_OPTIONS = subdir-objects AM_CFLAGS = -std=gnu99 -W -Wall -Wextra -Wno-unused-parameter AM_CPPFLAGS = -D_GNU_SOURCE -D_BSD_SOURCE -D_DEFAULT_SOURCE AM_CPPFLAGS += -I$(top_srcdir) -D_PIDFILEDIR=\"@runstatedir@\" sbin_PROGRAMS = mdnsd if ENABLE_MQUERY bin_PROGRAMS = mquery endif mdnsd_SOURCES = mdnsd.c mdnsd.h addr.c conf.c queue.h mcsock.c mcsock.h mdnsd_LDADD = ../libmdnsd/libmdnsd.la $(LIBS) $(LIBOBJS) mquery_SOURCES = mquery.c mcsock.c mcsock.h mquery_LDADD = ../libmdnsd/libmdnsd.la $(LIBS) $(LIBOBJS) mdnsd-0.12/src/addr.c000066400000000000000000000144771436331001000144120ustar00rootroot00000000000000/* * Copyright (c) 2018-2022 Joachim Wiberg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include /* needed for u_int on DragonFly BSD */ #include #include #include #include #include #include #include #include #include #include #include "mdnsd.h" static TAILQ_HEAD(iflist, iface) iface_list = TAILQ_HEAD_INITIALIZER(iface_list); struct iface *iface_iterator(int first) { static struct iface *next = NULL; struct iface *iface; if (first) iface = TAILQ_FIRST(&iface_list); else iface = next; /* prepare for next, in case iface is unlinked */ if (iface) next = TAILQ_NEXT(iface, link); return iface; } struct iface *iface_find(const char *ifname) { struct iface *iface; for (iface = iface_iterator(1); iface; iface = iface_iterator(0)) { if (strcmp(iface->ifname, ifname)) continue; return iface; } return NULL; } void iface_free(struct iface *iface) { if (!iface) return; TAILQ_REMOVE(&iface_list, iface, link); free(iface); } static void mark(void) { struct iface *iface; for (iface = iface_iterator(1); iface; iface = iface_iterator(0)) { iface->unused = 1; iface->inaddr_old = iface->inaddr; memset(&iface->inaddr, 0, sizeof(iface->inaddr)); iface->in6addr_old = iface->in6addr; memset(&iface->in6addr, 0, sizeof(iface->in6addr)); } } static int sweep(void) { struct iface *iface; int changed = 0; for (iface = iface_iterator(1); iface; iface = iface_iterator(0)) { if (iface->unused) continue; if (!iface->changed) continue; if (iface->inaddr.s_addr == iface->inaddr_old.s_addr && IN6_ARE_ADDR_EQUAL(&iface->in6addr, &iface->in6addr_old)) { iface->changed = 0; continue; } changed++; } return changed; } void iface_init(char *ifname) { struct ifaddrs *ifaddr, *ifa; struct iface *iface = NULL; int rc = -1; rc = getifaddrs(&ifaddr); if (rc) { ERR("Failed fetching system interfaces: %s", strerror(errno)); return; } mark(); for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { struct in6_addr ina; char buf[INET6_ADDRSTRLEN]; if (!ifa->ifa_addr) continue; if (ifa->ifa_flags & IFF_LOOPBACK) continue; /* skip for now, mDNSResponder has it as fallback */ if (!(ifa->ifa_flags & IFF_MULTICAST)) continue; if (!(ifa->ifa_flags & IFF_UP)) continue; /* skip for now, not enabled */ if (ifa->ifa_addr->sa_family != AF_INET && ifa->ifa_addr->sa_family != AF_INET6) continue; if (ifname && strcmp(ifname, ifa->ifa_name)) continue; /* Validate IP address */ socklen_t salen = ifa->ifa_addr->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); rc = getnameinfo(ifa->ifa_addr, salen, buf, sizeof(buf), NULL, 0, NI_NUMERICHOST); if (rc) continue; /* Use address from getnameinfo() */ char* pc = strchr(buf, '%'); if (pc != NULL) /* inet_pton cannot handle the interface part of a link local IPv6 address. */ *pc = '\0'; if (inet_pton(ifa->ifa_addr->sa_family, buf, &ina) <= 0) continue; if (!iface) iface = iface_find(ifa->ifa_name); if (!iface) { iface = calloc(1, sizeof(*iface)); if (!iface) { ERR("Failed allocating memory for iface %s: %s", ifa->ifa_name, strerror(errno)); exit(1); } DBG("Creating iface instance for interface %s, address %s", ifa->ifa_name, buf); TAILQ_INSERT_TAIL(&iface_list, iface, link); strlcpy(iface->ifname, ifa->ifa_name, sizeof(iface->ifname)); iface->ifindex = if_nametoindex(ifa->ifa_name); iface->hostid = 1; iface->sd = -1; } else { iface->unused = 0; } if (ifa->ifa_addr->sa_family == AF_INET) { if (iface->inaddr.s_addr != ina.s6_addr32[0]) { if (is_zeronet(&iface->inaddr) || is_linklocal(&iface->inaddr)) { iface->inaddr.s_addr = ina.s6_addr32[0]; iface->changed = 1; } } } else { if (! IN6_ARE_ADDR_EQUAL(&iface->in6addr, &ina)) { if (IN6_IS_ADDR_UNSPECIFIED(&iface->in6addr) /* Everything is better than no address (::) */ /* Any address is preferred over link local address */ || IN6_IS_ADDR_LINKLOCAL(&iface->in6addr) /* Any address is preferred over a site local address except for a link local address. */ || (IN6_IS_ADDR_SITELOCAL(&iface->in6addr) && !IN6_IS_ADDR_LINKLOCAL(&ina)) /* A unique local address shall only be overwritten by a global address. */ || ((iface->in6addr.s6_addr[0] & 0xfe) == 0xfc && !IN6_IS_ADDR_SITELOCAL(&ina) && !IN6_IS_ADDR_LINKLOCAL(&ina)) ) { iface->in6addr = ina; iface->changed = 1; } } } /* prepare for next */ if (!ifname) iface = NULL; } freeifaddrs(ifaddr); sweep(); } void iface_exit(void) { struct iface *iface, *tmp; TAILQ_FOREACH_SAFE(iface, &iface_list, link, tmp) { TAILQ_REMOVE(&iface_list, iface, link); free(iface); } } mdnsd-0.12/src/conf.c000066400000000000000000000175401436331001000144170ustar00rootroot00000000000000/* * Copyright (c) 2018-2022 Joachim Wiberg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include "mdnsd.h" struct conf_srec { char *type; char *name; int port; char *target; char *cname; char *txt[42]; size_t txt_num; }; static char *chomp(char *str) { char *p; if (!str || strlen(str) < 1) { errno = EINVAL; return NULL; } p = str + strlen(str) - 1; while (*p == '\n') *p-- = 0; return str; } static int match(char *key, char *token) { return !strcmp(key, token); } static void read_line(char *line, struct conf_srec *srec) { char *arg, *token; arg = chomp(line); DBG("Got line: '%s'", line); if (line[0] == '#') return; token = strsep(&arg, " \t"); if (!token || !arg) { DBG("Skipping, token:%s, arg:%s", token, arg); return; } if (match(token, "type")) { free(srec->type); srec->type = strdup(arg); } else if (match(token, "name")) { free(srec->name); srec->name = strdup(arg); } else if (match(token, "port")) { char *end; srec->port = (int)strtol(arg, &end, 10); if (*end) { DBG("Bad port number: %s", arg); return; } } else if (match(token, "target")) { free(srec->target); srec->target = strdup(arg); } else if (match(token, "cname")) { free(srec->cname); srec->cname = strdup(arg); } else if (match(token, "txt") && srec->txt_num < NELEMS(srec->txt)) { srec->txt[srec->txt_num++] = strdup(arg); } } static int parse(char *fn, struct conf_srec *srec) { FILE *fp; char line[256]; DBG("Attempting to read %s ...", fn); fp = fopen(fn, "r"); if (!fp) return 1; while (fgets(line, sizeof(line), fp)) read_line(line, srec); fclose(fp); DBG("Finished reading %s ...", fn); return 0; } /* Create a new record, or update an existing one */ mdns_record_t *record(struct iface *iface, int shared, char *host, const char *name, unsigned short type, unsigned long ttl) { mdns_daemon_t *d = iface->mdns; mdns_record_t *r; r = mdnsd_find(d, name, type); while (r != NULL) { const mdns_answer_t *a; if (!host) break; a = mdnsd_record_data(r); if (!a) { r = mdnsd_record_next(r); continue; } if (a->rdname && strcmp(a->rdname, host)) { r = mdnsd_record_next(r); continue; } return r; } if (!r) { if (shared) r = mdnsd_shared(d, name, type, ttl); else r = mdnsd_unique(d, name, type, ttl, mdnsd_conflict, iface); if (host) mdnsd_set_host(d, r, host); } return r; } static int load(struct iface *iface, char *path, char *hostname) { mdns_daemon_t *d = iface->mdns; struct conf_srec srec; unsigned char *packet; mdns_record_t *r; size_t i; xht_t *h; char hlocal[256], nlocal[256], tlocal[256]; int len = 0; memset(&srec, 0, sizeof(srec)); if (parse(path, &srec)) { ERR("Failed reading %s: %s", path, strerror(errno)); return 1; } if (!srec.name) srec.name = strdup(hostname); if (!srec.type) srec.type = strdup("_http._tcp"); snprintf(hlocal, sizeof(hlocal), "%s.%s.local.", srec.name, srec.type); snprintf(nlocal, sizeof(nlocal), "%s.local.", srec.name); snprintf(tlocal, sizeof(tlocal), "%s.local.", srec.type); if (!srec.target) srec.target = strdup(hlocal); /* Announce that we have a $type service */ record(iface, 1, tlocal, DISCO_NAME, QTYPE_PTR, 120); record(iface, 1, srec.target, tlocal, QTYPE_PTR, 120); r = record(iface, 0, NULL, hlocal, QTYPE_SRV, 120); mdnsd_set_srv(d, r, 0, 0, srec.port, nlocal); /* If an IPv4 address is already set, add an A record for it. */ struct in_addr ipv4addr = mdnsd_get_address(d); if (ipv4addr.s_addr != 0) { r = record(iface, 0, NULL, nlocal, QTYPE_A, 120); mdnsd_set_ip(d, r, ipv4addr); } /* If an IPv6 address is already set, add an AAAA record for it. */ struct in6_addr ipv6addr = mdnsd_get_ipv6_address(d); if (!IN6_IS_ADDR_UNSPECIFIED(&ipv6addr)) { r = record(iface, 0, NULL, nlocal, QTYPE_AAAA, 120); mdnsd_set_ipv6(d, r, ipv6addr); } if (srec.cname) record(iface, 1, srec.cname, nlocal, QTYPE_CNAME, 120); r = record(iface, 0, NULL, hlocal, QTYPE_TXT, 4500); h = xht_new(11); for (i = 0; i < srec.txt_num; i++) { char *ptr; ptr = strchr(srec.txt[i], '='); if (!ptr) continue; *ptr++ = 0; xht_set(h, srec.txt[i], ptr); } packet = sd2txt(h, &len); xht_free(h); mdnsd_set_raw(d, r, (char *)packet, len); free(packet); free(srec.type); free(srec.name); free(srec.target); free(srec.cname); for (i = 0; i < NELEMS(srec.txt); i++) free(srec.txt[i]); return 0; } int conf_init(struct iface *iface, char *path, char *hostnm) { char hostname[_POSIX_HOST_NAME_MAX]; int hostid = iface->hostid; struct stat st; int rc = 0; if (hostnm) { strlcpy(hostname, hostnm, sizeof(hostname)); } else { /* apparently gethostname() can fail ... */ if (gethostname(hostname, sizeof(hostname)) == -1) strlcpy(hostname, "default", sizeof(hostname)); } /* uniqify hostname by appending -hostid, e.g., default-2 */ if (hostid > 1) { size_t hlen, slen; char suffix[16]; slen = snprintf(suffix, sizeof(suffix), "-%d", hostid) + 1; hlen = strlen(hostname); if (hlen + slen >= sizeof(hostname)) hlen = sizeof(hostname) - slen; strlcpy(&hostname[hlen], suffix, sizeof(hostname) - hlen); } if (stat(path, &st)) { if (ENOENT == errno) ERR("Services directory %s, missing or unconfigured.", path); else ERR("Cannot determine path type: %s", strerror(errno)); return 1; } if (S_ISDIR(st.st_mode)) { glob_t gl; size_t i; char pat[strlen(path) + 64]; int flags = GLOB_ERR; strlcpy(pat, path, sizeof(pat)); if (pat[strlen(path) - 1] != '/') strcat(pat, "/"); strcat(pat, "*.service"); #ifdef GLOB_TILDE /* E.g. musl libc < 1.1.21 does not have this GNU LIBC extension */ flags |= GLOB_TILDE; #else /* Simple homegrown replacement that at least handles leading ~/ */ if (!strncmp(pat, "~/", 2)) { const char *home; home = getenv("HOME"); if (home) { memmove(pat + strlen(home), pat, strlen(path)); memcpy(pat, home, strlen(home)); } } #endif if (glob(pat, flags, NULL, &gl)) { ERR("No .service files found in %s", pat); return 1; } for (i = 0; i < gl.gl_pathc; i++) rc |= load(iface, gl.gl_pathv[i], hostname); globfree(&gl); } else rc |= load(iface, path, hostname); return rc; } mdnsd-0.12/src/mcsock.c000066400000000000000000000141201436331001000147400ustar00rootroot00000000000000/* * Copyright (c) 2022 Florian Zschocke * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include /* needed for u_int on DragonFly BSD */ #include #include #include #include #include #include #include #include #include "libmdnsd/mdnsd.h" #include "mcsock.h" static struct in_addr get_addr(const char *ifname) { struct ifaddrs *ifaddr, *ifa; struct in_addr addr; addr.s_addr = htonl(INADDR_ANY); if (getifaddrs(&ifaddr) < 0) { WARN("Failed getting interfaces: %s", strerror(errno)); return addr; } for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr != NULL && ifa->ifa_addr->sa_family == AF_INET && strcmp(ifname, ifa->ifa_name) == 0) { addr = ((struct sockaddr_in*)ifa->ifa_addr)->sin_addr; break; } } freeifaddrs(ifaddr); return addr; } static int mc_socket(struct ifnfo *iface, unsigned char ttl) { #ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX struct ip_mreqn imr ={ 0 }; #else struct ip_mreq imr = { 0 }; imr.imr_interface.s_addr = htonl(INADDR_ANY); #endif const unsigned char ena = 1; const int on = 1; #ifdef IP_MULTICAST_ALL const int off = 0; #endif struct sockaddr_in sin; socklen_t len; int unicast_ttl = 255; int bufsiz; int sd; sd = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0); if (sd < 0) { ERR("Failed creating UDP socket: %s", strerror(errno)); return -1; } #ifdef SO_REUSEPORT if (setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on))) WARN("Failed setting SO_REUSEPORT: %s", strerror(errno)); #endif if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) WARN("Failed setting SO_REUSEADDR: %s", strerror(errno)); /* Double the size of the receive buffer (getsockopt() returns the double) */ len = sizeof(bufsiz); if (!getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &bufsiz, &len)) { if (setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &bufsiz, sizeof(bufsiz))) INFO("Failed doubling the size of the receive buffer: %s", strerror(errno)); } if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, &ena, sizeof(ena))) WARN("Failed enabling IP_MULTICAST_LOOP on %s: %s", iface->ifname, strerror(errno)); #ifdef IP_MULTICAST_ALL if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_ALL, &off, sizeof(off))) WARN("Failed disabling IP_MULTICAST_ALL on %s: %s", iface->ifname, strerror(errno)); #endif /* * All traffic on mDNS is link-local only, so the default * TTL is set to 1. Some users may however want to route mDNS. */ if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl))) WARN("Failed setting IP_MULTICAST_TTL to %d: %s", ttl, strerror(errno)); /* mDNS also supports unicast, so we need a relevant TTL there too */ if (setsockopt(sd, IPPROTO_IP, IP_TTL, &unicast_ttl, sizeof(unicast_ttl))) WARN("Failed setting IP_TTL to %d: %s", unicast_ttl, strerror(errno)); if (iface) { if (iface->ifindex != 0 && iface->inaddr.s_addr == 0) { iface->inaddr = get_addr(iface->ifname); } /* Set interface for outbound multicast */ #ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX imr.imr_ifindex = iface->ifindex; if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, &imr, sizeof(imr))) WARN("Failed setting IP_MULTICAST_IF to %d: %s", iface->ifindex, strerror(errno)); #else imr.imr_interface = iface->inaddr; if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, &(iface->inaddr), sizeof(struct in_addr))) WARN("Failed setting IP_MULTICAST_IF to %s: %s", inet_ntoa(iface->inaddr), strerror(errno)); #endif #ifdef HAVE_SO_BINDTODEVICE /* Filter inbound traffic from anyone (ANY) to port 5353 on ifname */ if (setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &iface->ifname, strlen(iface->ifname))) INFO("Failed setting SO_BINDTODEVICE on %s: %s", iface->ifname, strerror(errno)); #endif } /* * Join mDNS link-local group on the given interface, that way * we can receive multicast without a proper net route (default * route or a 224.0.0.0/24 net route). */ imr.imr_multiaddr.s_addr = inet_addr("224.0.0.251"); if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(imr))) WARN("Failed joining mDNS group 224.0.0.251: %s", strerror(errno)); memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(5353); if (bind(sd, (struct sockaddr *)&sin, sizeof(sin))) { close(sd); ERR("Failed binding socket to *:5353: %s", strerror(errno)); return -1; } INFO("Bound to *:5353 on iface %s", iface->ifname); return sd; } int mdns_socket(struct ifnfo *iface, unsigned char ttl) { /* Default to TTL of 1 for mDNS as it is link-local */ if (ttl == 0) ttl = 1; return mc_socket(iface, ttl); } mdnsd-0.12/src/mcsock.h000066400000000000000000000043471436331001000147570ustar00rootroot00000000000000/* * Copyright (c) 2022 Florian Zschocke * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef MDNSD_MCSOCK_H_ #define MDNSD_MCSOCK_H_ #include /* in_addr */ #include /* IFNAMSIZ */ struct ifnfo { char ifname[IFNAMSIZ]; /**< Interface name */ int ifindex; /**< Physical interface index */ struct in_addr inaddr; /**< IPv4 address for interface */ }; /** * Create multicast socket for mDNS. * * @param iface Specify if socket should be bound to specific interface * @param ttl Multicast TTL, 0 for default (1) * @return socket file descriptor or <0 in case or error */ int mdns_socket(struct ifnfo *iface, unsigned char ttl); #endif /* MDNSD_MCSOCK_H_ */ mdnsd-0.12/src/mdnsd.c000066400000000000000000000225271436331001000146000ustar00rootroot00000000000000/* * Copyright (c) 2003 Jeremie Miller * Copyright (c) 2016-2022 Joachim Wiberg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "mcsock.h" #include "mdnsd.h" #define SYS_INTERVAL 10 /* System inteface poll interval */ volatile sig_atomic_t running = 1; volatile sig_atomic_t reload = 0; char *prognm = PACKAGE_NAME; char *hostnm = NULL; char *ifname = NULL; char *path = NULL; int background = 1; int logging = 1; int ttl = 255; /* * Create a multicast socket and bind it to the given interface. * Conclude by joining 224.0.0.251:5353 to hear others. */ static int multicast_socket(struct iface *iface, unsigned char ttl) { struct ifnfo ifa; memcpy(ifa.ifname, iface->ifname, sizeof(ifa.ifname)); ifa.ifindex = iface->ifindex; ifa.inaddr = iface->inaddr; return mdns_socket(&ifa, ttl); } void mdnsd_conflict(char *name, int type, void *arg) { struct iface *iface = (struct iface *)arg; WARN("%s: conflicting name detected %s for type %d, reloading config ...", iface->ifname, name, type); if (!reload) { iface->hostid++; reload = 1; } } static void record_received(const struct resource *r, void *data) { char ipinput[INET6_ADDRSTRLEN]; switch(r->type) { case QTYPE_A: inet_ntop(AF_INET, &(r->known.a.ip), ipinput, INET_ADDRSTRLEN); DBG("Got %s: A %s->%s", r->name, r->known.a.name, ipinput); break; case QTYPE_AAAA: inet_ntop(AF_INET6, &(r->known.aaaa.ip6), ipinput, INET6_ADDRSTRLEN); DBG("Got %s: AAAA %s->%s", r->name, r->known.aaaa.name, ipinput); break; case QTYPE_NS: DBG("Got %s: NS %s", r->name, r->known.ns.name); break; case QTYPE_CNAME: DBG("Got %s: CNAME %s", r->name, r->known.cname.name); break; case QTYPE_PTR: DBG("Got %s: PTR %s", r->name, r->known.ptr.name); break; case QTYPE_TXT: DBG("Got %s: TXT %s", r->name, r->rdata); break; case QTYPE_SRV: DBG("Got %s: SRV %d %d %d %s", r->name, r->known.srv.priority, r->known.srv.weight, r->known.srv.port, r->known.srv.name); break; default: DBG("Got %s: unknown", r->name); } } static void free_iface(struct iface *iface) { mdnsd_shutdown(iface->mdns); mdnsd_free(iface->mdns); iface->mdns = NULL; if (iface->sd >= 0) close(iface->sd); iface_free(iface); } static void setup_iface(struct iface *iface) { if (!iface->changed) return; if (iface->unused) { free_iface(iface); return; } if (!iface->mdns) { iface->mdns = mdnsd_new(QCLASS_IN, 1000); if (!iface->mdns) { ERR("Failed creating mDNS context for interface %s: %s", iface->ifname, strerror(errno)); exit(1); } mdnsd_set_address(iface->mdns, iface->inaddr); mdnsd_set_ipv6_address(iface->mdns, iface->in6addr); conf_init(iface, path, hostnm); mdnsd_register_receive_callback(iface->mdns, record_received, NULL); } if (iface->sd < 0) { iface->sd = multicast_socket(iface, (unsigned char)ttl); if (iface->sd < 0) { ERR("Failed creating socket: %s", strerror(errno)); exit(1); } } mdnsd_set_address(iface->mdns, iface->inaddr); mdnsd_set_ipv6_address(iface->mdns, iface->in6addr); iface->changed = 0; } static int sys_timeout(int *timeout) { static struct timespec before; struct timespec now; if (*timeout == 0) { clock_gettime(CLOCK_MONOTONIC, &before); *timeout = SYS_INTERVAL; } else { clock_gettime(CLOCK_MONOTONIC, &now); if (before.tv_sec + SYS_INTERVAL <= now.tv_sec) { before = now; return 1; } } return 0; } static void sys_init(void) { struct iface *iface; /* Initialize or check if IP address changed, needed to update A records */ iface_init(ifname); for (iface = iface_iterator(1); iface; iface = iface_iterator(0)) setup_iface(iface); } static void done(int signo) { running = 0; } static void reconf(int signo) { reload = 1; } static void sig_init(void) { signal(SIGINT, done); signal(SIGHUP, reconf); signal(SIGQUIT, done); signal(SIGTERM, done); } static int usage(int code) { printf("Usage: %s [-hnsv] " #ifdef HAVE_SO_BINDTODEVICE "[-i IFACE] " #endif "[-l LEVEL] [-t TTL] [PATH]\n" "\n" "Options:\n" " -h This help text\n" #ifdef HAVE_SO_BINDTODEVICE " -i IFACE Interface to announce services on, and get address from\n" #endif " -l LEVEL Set log level: none, err, notice (default), info, debug\n" " -n Run in foreground, do not detach from controlling terminal\n" " -s Use syslog even if running in foreground\n" " -t TTL Set TTL of mDNS packets, default: 1 (link-local only)\n" " -v Show program version\n" "\n" "Arguments:\n" " PATH Path to mDNS-SD .service files, default: /etc/mdns.d\n" "\n" "Bug report address: %-40s\n", prognm, PACKAGE_BUGREPORT); return code; } static char *progname(char *arg0) { char *nm; nm = strrchr(arg0, '/'); if (nm) nm++; else nm = arg0; return nm; } int main(int argc, char *argv[]) { struct timeval tv = { 0 }; struct iface *iface; fd_set fds; int timeout = 0; int c, rc; prognm = progname(argv[0]); while ((c = getopt(argc, argv, "H:h" #ifdef HAVE_SO_BINDTODEVICE "i:" #endif "l:nst:v?")) != EOF) { switch (c) { case 'H': hostnm = optarg; break; case 'h': case '?': return usage(0); #ifdef HAVE_SO_BINDTODEVICE case 'i': ifname = optarg; break; #endif case 'l': if (-1 == mdnsd_log_level(optarg)) return usage(1); break; case 'n': background = 0; logging--; break; case 's': logging++; break; case 't': /* XXX: Use strtonum() instead */ ttl = atoi(optarg); if (ttl < 1 || ttl > 255) return usage(1); break; case 'v': puts(PACKAGE_VERSION); return 0; default: break; } } if (optind < argc) path = argv[optind]; else path = "/etc/mdns.d"; if (logging > 0) mdnsd_log_open(prognm); if (background) { DBG("Daemonizing ..."); if (-1 == daemon(0, 0)) { ERR("Failed daemonizing: %s", strerror(errno)); return 1; } } NOTE("%s starting.", PACKAGE_STRING); sig_init(); sys_init(); pidfile(PACKAGE_NAME); while (running) { int nfds = 0; FD_ZERO(&fds); for (iface = iface_iterator(1); iface; iface = iface_iterator(0)) { if (iface->sd < 0 || iface->unused) continue; FD_SET(iface->sd, &fds); if (iface->sd > nfds) nfds = iface->sd; } if (nfds > 0) nfds++; DBG("Going to sleep for %d sec ...", (int)tv.tv_sec); rc = select(nfds, &fds, NULL, NULL, &tv); if ((rc < 0 && EINTR == errno) || reload) { if (!running) break; if (reload) { sys_init(); for (iface = iface_iterator(1); iface; iface = iface_iterator(0)) { records_clear(iface->mdns); conf_init(iface, path, hostnm); } pidfile(PACKAGE_NAME); reload = 0; } continue; } if (sys_timeout(&timeout)) sys_init(); tv.tv_sec = timeout; for (iface = iface_iterator(1); iface; iface = iface_iterator(0)) { struct timeval next; DBG("Checking interface %s for activity ...", iface->ifname); if (iface->unused || iface->sd < 0) continue; rc = mdnsd_step(iface->mdns, iface->sd, FD_ISSET(iface->sd, &fds), true, &next); if (!rc) { if (tv.tv_sec > next.tv_sec) tv = next; continue; } if (rc == 1) ERR("Failed reading from socket %d: %s", errno, strerror(errno)); if (rc == 2) ERR("Failed writing to socket: %s", strerror(errno)); free_iface(iface); } } NOTE("%s exiting.", PACKAGE_STRING); for (iface = iface_iterator(1); iface; iface = iface_iterator(0)) free_iface(iface); iface_exit(); return 0; } mdnsd-0.12/src/mdnsd.h000066400000000000000000000074151436331001000146040ustar00rootroot00000000000000/* * Copyright (c) 2003 Jeremie Miller * Copyright (c) 2016-2022 Joachim Wiberg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef MDNSD_H_ #define MDNSD_H_ #include "config.h" #include /* IFNAMSIZ */ #include #include #include #include "queue.h" /* From The Practice of Programming, by Kernighan and Pike */ #ifndef NELEMS #define NELEMS(array) (sizeof(array) / sizeof((array)[0])) #endif #ifndef IN_ZERONET #define IN_ZERONET(addr) ((addr & IN_CLASSA_NET) == 0) #endif #ifndef IN_LOOPBACK #define IN_LOOPBACK(addr) ((addr & IN_CLASSA_NET) == 0x7f000000) #endif #ifndef IN_LINKLOCAL #define IN_LINKLOCALNETNUM 0xa9fe0000 #define IN_LINKLOCAL(addr) ((addr & IN_CLASSB_NET) == IN_LINKLOCALNETNUM) #endif struct iface { TAILQ_ENTRY(iface) link; char unused; char changed; char ifname[IFNAMSIZ]; int ifindex; /* Physical interface index */ struct in_addr inaddr; /* == 0 for non IP interfaces */ struct in_addr inaddr_old; struct in6_addr in6addr; /* == :: for non IP interfaces */ struct in6_addr in6addr_old; int sd; mdns_daemon_t *mdns; int hostid; /* init to 1, +1 on conflict */ }; void mdnsd_conflict(char *name, int type, void *arg); /* addr.c */ struct iface *iface_iterator(int first); struct iface *iface_find(const char *ifname); void iface_free(struct iface *iface); void iface_init(char *ifname); void iface_exit(void); /* conf.c */ int conf_init(struct iface *iface, char *path, char *hostnm); /* replacement functions for systems that don't have them */ #ifndef HAVE_PIDFILE int pidfile(const char *basename); #endif #ifndef HAVE_STRLCPY size_t strlcpy(char *dst, const char *src, size_t siz); #endif static inline int is_linklocal(struct in_addr *ina) { return IN_LINKLOCAL(ntohl(ina->s_addr)); } static inline int is_zeronet(struct in_addr *ina) { return IN_ZERONET(ntohl(ina->s_addr)); } static inline int is_add_valid(struct in_addr *ina) { in_addr_t addr; addr = ntohl(ina->s_addr); if (IN_ZERONET(addr) || IN_LOOPBACK(addr) || IN_LINKLOCAL(addr)) return 0; return 1; } #endif /* MDNSD_H_ */ mdnsd-0.12/src/mquery.c000066400000000000000000000163501436331001000150120ustar00rootroot00000000000000/* * Copyright (c) 2003 Jeremie Miller * Copyright (c) 2016-2022 Joachim Wiberg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "mcsock.h" char *prognm = "mquery"; mdns_daemon_t *d; int simple; #ifndef HAVE_STRLCPY size_t strlcpy(char *dst, const char *src, size_t siz); #endif /* Find default outbound *LAN* interface, i.e. skipping tunnels */ static char *getifname(char *ifname, size_t len) { uint32_t dest, gw, mask; char buf[256], name[17]; FILE *fp; int found = 0; fp = fopen("/proc/net/route", "r"); if (!fp) return NULL; while (fgets(buf, sizeof(buf), fp) != NULL) { int rc, flags, cnt, use, metric, mtu, win, irtt; rc = sscanf(buf, "%16s %X %X %X %d %d %d %X %d %d %d\n", name, &dest, &gw, &flags, &cnt, &use, &metric, &mask, &mtu, &win, &irtt); if (rc < 10 || !(flags & 1)) /* IFF_UP */ continue; if (dest != 0 || mask != 0) continue; if (!ifname[0] || !strncmp(ifname, "tun", 3)) { strlcpy(ifname, name, len); found = 1; break; } } fclose(fp); if (found) return ifname; return NULL; } static const char *type2str(int type) { static char str[20] = { 0 }; switch(type) { case QTYPE_A: return "A (1)"; case QTYPE_NS: return "NS (2)"; case QTYPE_CNAME: return "CNAME (5)"; case QTYPE_PTR: return "TR (12)"; case QTYPE_TXT: return "TXT (16)"; case QTYPE_AAAA: return "AAAA (28)"; case QTYPE_SRV: return "SRV (33)"; case QTYPE_ANY: return "ANY (255)"; default: snprintf(str, sizeof(str), "UNKNOWN (%d)", type); break; } return str; } /* Print an answer */ static int ans(mdns_answer_t *a, void *arg) { int now; char ipinput[INET6_ADDRSTRLEN]; if (a->ttl == 0) now = 0; else now = a->ttl - time(0); if (!simple) { char *spec = (char *)arg; if (a->type != QTYPE_PTR) return 0; if (!spec) mdnsd_query(d, a->rdname, a->type, ans, a->rdname); printf("+ %s (%s)\n", a->rdname, inet_ntoa(a->ip)); return 0; } switch (a->type) { case QTYPE_A: inet_ntop(AF_INET, &(a->ip), ipinput, INET_ADDRSTRLEN); printf("A %s for %d seconds to ip %s\n", a->name, now, ipinput); break; case QTYPE_AAAA: inet_ntop(AF_INET6, &(a->ip6), ipinput, INET6_ADDRSTRLEN); printf("AAAA %s for %d seconds to ip %s\n", a->name, now, ipinput); break; case QTYPE_PTR: printf("PTR %s for %d seconds to %s\n", a->name, now, a->rdname); break; case QTYPE_SRV: printf("SRV %s for %d seconds to %s:%d\n", a->name, now, a->rdname, a->srv.port); break; default: printf("%s %s for %d seconds with %d data\n", type2str(a->type), a->name, now, a->rdlen); } return 0; } /* Create multicast 224.0.0.251:5353 socket */ static int msock(char *ifname) { struct ifnfo ifa = { 0 }; if (ifname) { memcpy(ifa.ifname, ifname, sizeof(ifa.ifname)); ifa.ifindex = if_nametoindex(ifname); return mdns_socket(&ifa, 0); } return mdns_socket(NULL, 0); } static int usage(int code) { /* mquery -t 12 _http._tcp.local. */ printf("usage: mquery [-hsv] " #ifdef HAVE_SO_BINDTODEVICE "[-i IFNAME] " #endif "[-l LEVEL] [-t TYPE] [-w SEC] [NAME]\n"); return code; } int main(int argc, char *argv[]) { struct message m; struct in_addr ip; unsigned short port; ssize_t bsize; socklen_t ssize; unsigned char buf[MAX_PACKET_LEN]; char default_iface[IFNAMSIZ] = { 0 }; struct sockaddr_in from, to; char *name = DISCO_NAME; char *ifname = NULL; int type = QTYPE_PTR; /* 12 */ time_t start; int wait = 0; fd_set fds; int sd, c; while ((c = getopt(argc, argv, "h?" #ifdef HAVE_SO_BINDTODEVICE "i:" #endif "l:st:vw:")) != EOF) { switch (c) { case 'h': case '?': return usage(0); #ifdef HAVE_SO_BINDTODEVICE case 'i': ifname = optarg; break; #endif case 'l': if (-1 == mdnsd_log_level(optarg)) return usage(1); break; case 's': simple = 1; break; case 't': type = atoi(optarg); break; case 'v': puts(PACKAGE_VERSION); return 0; case 'w': wait = atoi(optarg); break; default: return usage(1); } } if (optind < argc) name = argv[optind]; if (!ifname) ifname = getifname(default_iface, sizeof(default_iface)); sd = msock(ifname); if (sd == -1) { printf("Failed creating multicast socket: %s\n", strerror(errno)); return 1; } d = mdnsd_new(1, 1000); if (!d) return 1; printf("Querying for %s type %d ... press Ctrl-C to stop\n", name, type); start = time(NULL); mdnsd_query(d, name, type, ans, NULL); while (1) { struct timeval *tv = mdnsd_sleep(d); FD_ZERO(&fds); FD_SET(sd, &fds); select(sd + 1, &fds, 0, 0, tv); if (FD_ISSET(sd, &fds)) { ssize = sizeof(struct sockaddr_in); while ((bsize = recvfrom(sd, buf, MAX_PACKET_LEN, 0, (struct sockaddr *)&from, &ssize)) > 0) { memset(&m, 0, sizeof(struct message)); if (message_parse(&m, buf) == 0) mdnsd_in(d, &m, from.sin_addr, from.sin_port); } if (bsize < 0 && errno != EAGAIN) { printf("Failed reading from socket %d: %s\n", errno, strerror(errno)); return 1; } } while (mdnsd_out(d, &m, &ip, &port)) { int len = message_packet_len(&m); memset(&to, 0, sizeof(to)); to.sin_family = AF_INET; to.sin_port = port; to.sin_addr = ip; if (sendto(sd, message_packet(&m), len, 0, (struct sockaddr *)&to, sizeof(struct sockaddr_in)) != len) { printf("Failed writing to socket: %s\n", strerror(errno)); return 1; } } if (wait && (time(NULL) - start >= wait)) break; } mdnsd_shutdown(d); mdnsd_free(d); return 0; } mdnsd-0.12/src/queue.h000066400000000000000000000436211436331001000146220ustar00rootroot00000000000000/* $OpenBSD: queue.h,v 1.43 2015/12/28 19:38:40 millert Exp $ */ /* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ /* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)queue.h 8.5 (Berkeley) 8/20/94 */ #ifndef _SYS_QUEUE_H_ #define _SYS_QUEUE_H_ /* * This file defines five types of data structures: singly-linked lists, * lists, simple queues, tail queues and XOR simple queues. * * * A singly-linked list is headed by a single forward pointer. The elements * are singly linked for minimum space and pointer manipulation overhead at * the expense of O(n) removal for arbitrary elements. New elements can be * added to the list after an existing element or at the head of the list. * Elements being removed from the head of the list should use the explicit * macro for this purpose for optimum efficiency. A singly-linked list may * only be traversed in the forward direction. Singly-linked lists are ideal * for applications with large datasets and few or no removals or for * implementing a LIFO queue. * * A list is headed by a single forward pointer (or an array of forward * pointers for a hash table header). The elements are doubly linked * so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before * or after an existing element or at the head of the list. A list * may only be traversed in the forward direction. * * A simple queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are singly * linked to save space, so elements can only be removed from the * head of the list. New elements can be added to the list before or after * an existing element, at the head of the list, or at the end of the * list. A simple queue may only be traversed in the forward direction. * * A tail queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or * after an existing element, at the head of the list, or at the end of * the list. A tail queue may be traversed in either direction. * * An XOR simple queue is used in the same way as a regular simple queue. * The difference is that the head structure also includes a "cookie" that * is XOR'd with the queue pointer (first, last or next) to generate the * real pointer value. * * For details on the use of these macros, see the queue(3) manual page. */ #if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) #define _Q_INVALIDATE(a) (a) = ((void *)-1) #else #define _Q_INVALIDATE(a) #endif /* * Singly-linked List definitions. */ #define SLIST_HEAD(name, type) \ struct name { \ struct type *slh_first; /* first element */ \ } #define SLIST_HEAD_INITIALIZER(head) \ { NULL } #define SLIST_ENTRY(type) \ struct { \ struct type *sle_next; /* next element */ \ } /* * Singly-linked List access methods. */ #define SLIST_FIRST(head) ((head)->slh_first) #define SLIST_END(head) NULL #define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) #define SLIST_FOREACH(var, head, field) \ for((var) = SLIST_FIRST(head); \ (var) != SLIST_END(head); \ (var) = SLIST_NEXT(var, field)) #define SLIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = SLIST_FIRST(head); \ (var) && ((tvar) = SLIST_NEXT(var, field), 1); \ (var) = (tvar)) /* * Singly-linked List functions. */ #define SLIST_INIT(head) { \ SLIST_FIRST(head) = SLIST_END(head); \ } #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ (elm)->field.sle_next = (slistelm)->field.sle_next; \ (slistelm)->field.sle_next = (elm); \ } while (0) #define SLIST_INSERT_HEAD(head, elm, field) do { \ (elm)->field.sle_next = (head)->slh_first; \ (head)->slh_first = (elm); \ } while (0) #define SLIST_REMOVE_AFTER(elm, field) do { \ (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ } while (0) #define SLIST_REMOVE_HEAD(head, field) do { \ (head)->slh_first = (head)->slh_first->field.sle_next; \ } while (0) #define SLIST_REMOVE(head, elm, type, field) do { \ if ((head)->slh_first == (elm)) { \ SLIST_REMOVE_HEAD((head), field); \ } else { \ struct type *curelm = (head)->slh_first; \ \ while (curelm->field.sle_next != (elm)) \ curelm = curelm->field.sle_next; \ curelm->field.sle_next = \ curelm->field.sle_next->field.sle_next; \ } \ _Q_INVALIDATE((elm)->field.sle_next); \ } while (0) /* * List definitions. */ #define LIST_HEAD(name, type) \ struct name { \ struct type *lh_first; /* first element */ \ } #define LIST_HEAD_INITIALIZER(head) \ { NULL } #define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ } /* * List access methods. */ #define LIST_FIRST(head) ((head)->lh_first) #define LIST_END(head) NULL #define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) #define LIST_NEXT(elm, field) ((elm)->field.le_next) #define LIST_FOREACH(var, head, field) \ for((var) = LIST_FIRST(head); \ (var)!= LIST_END(head); \ (var) = LIST_NEXT(var, field)) #define LIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = LIST_FIRST(head); \ (var) && ((tvar) = LIST_NEXT(var, field), 1); \ (var) = (tvar)) /* * List functions. */ #define LIST_INIT(head) do { \ LIST_FIRST(head) = LIST_END(head); \ } while (0) #define LIST_INSERT_AFTER(listelm, elm, field) do { \ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ (listelm)->field.le_next->field.le_prev = \ &(elm)->field.le_next; \ (listelm)->field.le_next = (elm); \ (elm)->field.le_prev = &(listelm)->field.le_next; \ } while (0) #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.le_prev = (listelm)->field.le_prev; \ (elm)->field.le_next = (listelm); \ *(listelm)->field.le_prev = (elm); \ (listelm)->field.le_prev = &(elm)->field.le_next; \ } while (0) #define LIST_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.le_next = (head)->lh_first) != NULL) \ (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ (head)->lh_first = (elm); \ (elm)->field.le_prev = &(head)->lh_first; \ } while (0) #define LIST_REMOVE(elm, field) do { \ if ((elm)->field.le_next != NULL) \ (elm)->field.le_next->field.le_prev = \ (elm)->field.le_prev; \ *(elm)->field.le_prev = (elm)->field.le_next; \ _Q_INVALIDATE((elm)->field.le_prev); \ _Q_INVALIDATE((elm)->field.le_next); \ } while (0) #define LIST_REPLACE(elm, elm2, field) do { \ if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ (elm2)->field.le_next->field.le_prev = \ &(elm2)->field.le_next; \ (elm2)->field.le_prev = (elm)->field.le_prev; \ *(elm2)->field.le_prev = (elm2); \ _Q_INVALIDATE((elm)->field.le_prev); \ _Q_INVALIDATE((elm)->field.le_next); \ } while (0) /* * Simple queue definitions. */ #define SIMPLEQ_HEAD(name, type) \ struct name { \ struct type *sqh_first; /* first element */ \ struct type **sqh_last; /* addr of last next element */ \ } #define SIMPLEQ_HEAD_INITIALIZER(head) \ { NULL, &(head).sqh_first } #define SIMPLEQ_ENTRY(type) \ struct { \ struct type *sqe_next; /* next element */ \ } /* * Simple queue access methods. */ #define SIMPLEQ_FIRST(head) ((head)->sqh_first) #define SIMPLEQ_END(head) NULL #define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) #define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) #define SIMPLEQ_FOREACH(var, head, field) \ for((var) = SIMPLEQ_FIRST(head); \ (var) != SIMPLEQ_END(head); \ (var) = SIMPLEQ_NEXT(var, field)) #define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = SIMPLEQ_FIRST(head); \ (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); \ (var) = (tvar)) /* * Simple queue functions. */ #define SIMPLEQ_INIT(head) do { \ (head)->sqh_first = NULL; \ (head)->sqh_last = &(head)->sqh_first; \ } while (0) #define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ (head)->sqh_last = &(elm)->field.sqe_next; \ (head)->sqh_first = (elm); \ } while (0) #define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.sqe_next = NULL; \ *(head)->sqh_last = (elm); \ (head)->sqh_last = &(elm)->field.sqe_next; \ } while (0) #define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ (head)->sqh_last = &(elm)->field.sqe_next; \ (listelm)->field.sqe_next = (elm); \ } while (0) #define SIMPLEQ_REMOVE_HEAD(head, field) do { \ if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ (head)->sqh_last = &(head)->sqh_first; \ } while (0) #define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \ == NULL) \ (head)->sqh_last = &(elm)->field.sqe_next; \ } while (0) #define SIMPLEQ_CONCAT(head1, head2) do { \ if (!SIMPLEQ_EMPTY((head2))) { \ *(head1)->sqh_last = (head2)->sqh_first; \ (head1)->sqh_last = (head2)->sqh_last; \ SIMPLEQ_INIT((head2)); \ } \ } while (0) /* * XOR Simple queue definitions. */ #define XSIMPLEQ_HEAD(name, type) \ struct name { \ struct type *sqx_first; /* first element */ \ struct type **sqx_last; /* addr of last next element */ \ unsigned long sqx_cookie; \ } #define XSIMPLEQ_ENTRY(type) \ struct { \ struct type *sqx_next; /* next element */ \ } /* * XOR Simple queue access methods. */ #define XSIMPLEQ_XOR(head, ptr) ((__typeof(ptr))((head)->sqx_cookie ^ \ (unsigned long)(ptr))) #define XSIMPLEQ_FIRST(head) XSIMPLEQ_XOR(head, ((head)->sqx_first)) #define XSIMPLEQ_END(head) NULL #define XSIMPLEQ_EMPTY(head) (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head)) #define XSIMPLEQ_NEXT(head, elm, field) XSIMPLEQ_XOR(head, ((elm)->field.sqx_next)) #define XSIMPLEQ_FOREACH(var, head, field) \ for ((var) = XSIMPLEQ_FIRST(head); \ (var) != XSIMPLEQ_END(head); \ (var) = XSIMPLEQ_NEXT(head, var, field)) #define XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = XSIMPLEQ_FIRST(head); \ (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1); \ (var) = (tvar)) /* * XOR Simple queue functions. */ #define XSIMPLEQ_INIT(head) do { \ arc4random_buf(&(head)->sqx_cookie, sizeof((head)->sqx_cookie)); \ (head)->sqx_first = XSIMPLEQ_XOR(head, NULL); \ (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ } while (0) #define XSIMPLEQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.sqx_next = (head)->sqx_first) == \ XSIMPLEQ_XOR(head, NULL)) \ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ (head)->sqx_first = XSIMPLEQ_XOR(head, (elm)); \ } while (0) #define XSIMPLEQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL); \ *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = XSIMPLEQ_XOR(head, (elm)); \ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ } while (0) #define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.sqx_next = (listelm)->field.sqx_next) == \ XSIMPLEQ_XOR(head, NULL)) \ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm)); \ } while (0) #define XSIMPLEQ_REMOVE_HEAD(head, field) do { \ if (((head)->sqx_first = XSIMPLEQ_XOR(head, \ (head)->sqx_first)->field.sqx_next) == XSIMPLEQ_XOR(head, NULL)) \ (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ } while (0) #define XSIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ if (((elm)->field.sqx_next = XSIMPLEQ_XOR(head, \ (elm)->field.sqx_next)->field.sqx_next) \ == XSIMPLEQ_XOR(head, NULL)) \ (head)->sqx_last = \ XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ } while (0) /* * Tail queue definitions. */ #define TAILQ_HEAD(name, type) \ struct name { \ struct type *tqh_first; /* first element */ \ struct type **tqh_last; /* addr of last next element */ \ } #define TAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).tqh_first } #define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; /* next element */ \ struct type **tqe_prev; /* address of previous next element */ \ } /* * Tail queue access methods. */ #define TAILQ_FIRST(head) ((head)->tqh_first) #define TAILQ_END(head) NULL #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) #define TAILQ_LAST(head, headname) \ (*(((struct headname *)((head)->tqh_last))->tqh_last)) /* XXX */ #define TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) #define TAILQ_EMPTY(head) \ (TAILQ_FIRST(head) == TAILQ_END(head)) #define TAILQ_FOREACH(var, head, field) \ for((var) = TAILQ_FIRST(head); \ (var) != TAILQ_END(head); \ (var) = TAILQ_NEXT(var, field)) #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = TAILQ_FIRST(head); \ (var) != TAILQ_END(head) && \ ((tvar) = TAILQ_NEXT(var, field), 1); \ (var) = (tvar)) #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ for((var) = TAILQ_LAST(head, headname); \ (var) != TAILQ_END(head); \ (var) = TAILQ_PREV(var, headname, field)) #define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ for ((var) = TAILQ_LAST(head, headname); \ (var) != TAILQ_END(head) && \ ((tvar) = TAILQ_PREV(var, headname, field), 1); \ (var) = (tvar)) /* * Tail queue functions. */ #define TAILQ_INIT(head) do { \ (head)->tqh_first = NULL; \ (head)->tqh_last = &(head)->tqh_first; \ } while (0) #define TAILQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ (head)->tqh_first->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (head)->tqh_first = (elm); \ (elm)->field.tqe_prev = &(head)->tqh_first; \ } while (0) #define TAILQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.tqe_next = NULL; \ (elm)->field.tqe_prev = (head)->tqh_last; \ *(head)->tqh_last = (elm); \ (head)->tqh_last = &(elm)->field.tqe_next; \ } while (0) #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ (elm)->field.tqe_next->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (listelm)->field.tqe_next = (elm); \ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ } while (0) #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ (elm)->field.tqe_next = (listelm); \ *(listelm)->field.tqe_prev = (elm); \ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ } while (0) #define TAILQ_REMOVE(head, elm, field) do { \ if (((elm)->field.tqe_next) != NULL) \ (elm)->field.tqe_next->field.tqe_prev = \ (elm)->field.tqe_prev; \ else \ (head)->tqh_last = (elm)->field.tqe_prev; \ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ _Q_INVALIDATE((elm)->field.tqe_prev); \ _Q_INVALIDATE((elm)->field.tqe_next); \ } while (0) #define TAILQ_REPLACE(head, elm, elm2, field) do { \ if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ (elm2)->field.tqe_next->field.tqe_prev = \ &(elm2)->field.tqe_next; \ else \ (head)->tqh_last = &(elm2)->field.tqe_next; \ (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ *(elm2)->field.tqe_prev = (elm2); \ _Q_INVALIDATE((elm)->field.tqe_prev); \ _Q_INVALIDATE((elm)->field.tqe_next); \ } while (0) #define TAILQ_CONCAT(head1, head2, field) do { \ if (!TAILQ_EMPTY(head2)) { \ *(head1)->tqh_last = (head2)->tqh_first; \ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ (head1)->tqh_last = (head2)->tqh_last; \ TAILQ_INIT((head2)); \ } \ } while (0) #endif /* !_SYS_QUEUE_H_ */ mdnsd-0.12/test/000077500000000000000000000000001436331001000135075ustar00rootroot00000000000000mdnsd-0.12/test/.gitignore000066400000000000000000000000141436331001000154720ustar00rootroot00000000000000*.log *.trs mdnsd-0.12/test/Makefile.am000066400000000000000000000006111436331001000155410ustar00rootroot00000000000000EXTRA_DIST = lib.sh discover.sh iprecords.sh lostif.sh CLEANFILES = *~ *.trs *.log if ENABLE_UNIT_TESTS SUBDIRS = src endif # This env. variable is only needed for `make distcheck` TESTS_ENVIRONMENT = top_srcdir=$(top_srcdir) unshare -mrun TEST_EXTENSIONS = .sh TESTS = discover.sh TESTS += iprecords.sh TESTS += lostif.sh mdnsd-0.12/test/discover.sh000077500000000000000000000002631436331001000156650ustar00rootroot00000000000000#!/bin/sh # Set up basic topology, start mdnsd and then use mqeuery to locate it. #set -x # shellcheck source=/dev/null . "$(dirname "$0")/lib.sh" topo basic mdnsd discover OK mdnsd-0.12/test/iprecords.sh000077500000000000000000000041271436331001000160440ustar00rootroot00000000000000#!/bin/sh # Set up basic topology, start mdnsd and then use mqeuery to query for A records. #set -x # shellcheck source=/dev/null . "$(dirname "$0")/lib.sh" topo basic mdnsd print "Starting mquery to query for A records ..." mquery -s -t 1 test.local. >"$DIR/result" || FAIL "Not found" # shellcheck disable=SC2154 grep -q "A test.local. .* $server_addr" "$DIR/result" || FAIL mdnsd_stop print "Adding IPv6 address to topology ..." nsenter --net="$server" -- ip -6 addr add "${server_addr_6}"/64 dev eth0 mdnsd print "Starting mquery to query for A records ..." mquery -s -t 1 test.local. >"$DIR/result" || FAIL "Not found" # shellcheck disable=SC2154 grep -q "A test.local. .* $server_addr" "$DIR/result" || FAIL print "Starting mquery to query for AAAA records ..." mquery -s -t 28 test.local. >"$DIR/result" || FAIL "Not found" # shellcheck disable=SC2154 grep -q "AAAA test.local. .* $server_addr_6" "$DIR/result" || FAIL print "Removing all IPv6 addresses from topology while mdnsd is running..." nsenter --net="$server" -- ip -6 addr flush dev eth0 collect print "Waiting 15 seconds for mdnsd to catch up..." sleep 15 stop_collect # Check that Goodbye packets (TTL == 0) were sent if [ -f "$DIR/pcap" ] ; then response=$(tshark -r "$DIR/pcap" -Y mdns -T fields -e dns.resp.ttl -e dns.aaaa 2>&1 | grep -v "root") [ -n "$response" ] || FAIL "No mDNS goodbye packets found" [ "${response}" = "0,0 ${server_addr_6},${server_addr_6}" ] || FAIL "mDNS packets did not match goodbye packet requirements" else echo "Unable to verify goodbye packets being sent" if ! command -v tshark &>/dev/null ; then echo "Program tshark is not installed. Cannot collect network packets." fi fi print "Starting mquery to query for A records ..." mquery -s -t 1 test.local. >"$DIR/result" || FAIL "Not found" # shellcheck disable=SC2154 grep -q "A test.local. .* $server_addr" "$DIR/result" || FAIL print "Starting mquery to query for AAAA records ..." mquery -s -t 28 test.local. >"$DIR/result" || FAIL # shellcheck disable=SC2154 grep -q "AAAA test.local. " "$DIR/result" && FAIL "No AAAA records should be sent anymore" OK mdnsd-0.12/test/lib.sh000077500000000000000000000144041436331001000146170ustar00rootroot00000000000000#!/bin/sh # Helper functions for testing mdnsd # Session set from Makefile before calling unshare -mrun if [ -z "$SESSION" ]; then SESSION=$(mktemp -d mdnsd.XXXXXXXX) TMPSESS=1 fi # Test name, used everywhere as /tmp/$NM/foo NM=$(basename "$0" .sh) SRC=${top_srcdir:-..} DIR="${SESSION}/${NM}" client="${DIR}/client" server="${DIR}/server" client_addr=192.168.42.101 server_addr=192.168.42.1 client_addr_ll6= server_addr_ll6= client_addr_6=2001:db8:0:f101::2a:65 server_addr_6=2001:db8:0:f101::2a:1 # Print heading for test phases print() { printf "\e[7m>> %-80s\e[0m\n" "$1" } SKIP() { print "TEST: SKIP" [ $# -gt 0 ] && echo "$*" exit 77 } FAIL() { print "TEST: FAIL" [ $# -gt 0 ] && echo "$*" exit 99 } OK() { print "TEST: OK" [ $# -gt 0 ] && echo "$*" exit 0 } # prefer source tree build, fall back to check one level up mdnsd() { ARGS=$@ bin="../src/mdnsd" [ -x "$bin" ] || SKIP "Cannot find mdnsd" print "Starting mdnsd ..." nsenter --net="$server" -- "$bin" -H test -n "${SRC}/examples" $ARGS & echo "$! mdnsd" >>"${DIR}/pids" sleep 1 } # stop all instances of the mdnsd mdnsd_stop() { [ -f "${DIR}/pids" ] || SKIP "Cannot find PID file" print "Stopping mdnsd ..." leftpids="" while read pid prog; do if [ x"$prog" = x"mdnsd" ] ; then kill "$pid" 2>/dev/null else leftpids="${leftpids}$pid $prog\n" fi done < "${DIR}/pids" echo -n "${leftpids}" > "${DIR}/pids" sleep 1 } # prefer source tree build, fall back to check one level up mquery() { bin="../src/mquery" # bin="/usr/bin/mdns-scan" [ -x "$bin" ] || SKIP "Cannot find mquery" nsenter --net="$client" -- "$bin" -i eth0 -w 2 "$@" } discover() { print "Starting mquery to locate mdnsd ..." mquery >"$DIR/result" || FAIL "Not found" # shellcheck disable=SC2154 grep -q "+ _ftp._tcp.local. ($server_addr)" "$DIR/result" || FAIL } # Gather a pcap of the session # Example: # collect eth0 -c10 'dst 224.0.0.251' # Dump: # tshark -r "$DIR/pcap" 2>/dev/null | grep foo # collect() { print "Starting collector on $client ..." nsenter --net="$client" -- tshark -w "$DIR/pcap" -lni eth0 2>/dev/null & echo "$! tshark" >> "$DIR/pids" sleep 2 } # stop all instances of the pcap collector stop_collect() { [ -f "${DIR}/pids" ] || SKIP "Cannot find PID file" print "Stopping collector ..." leftpids="" while read pid prog; do if [ x"$prog" = x"tshark" ] ; then kill "$pid" 2>/dev/null else leftpids="${leftpids}$pid $prog\n" fi done < "${DIR}/pids" echo -n "${leftpids}" > "${DIR}/pids" sleep 1 } # Set up two logically separated network namespaces, connected via a # VETH pair, to simulate a basic LAN with two devices; one for mdnsd # and one client (mquery) topo_basic() { local sllip local to if [ ! -e "$server" ]; then touch "$server" unshare --net="$server" -- ip link set lo up fi nsenter --net="$server" -- ip link add eth0 type veth peer tmp0 nsenter --net="$server" -- ip link set tmp0 netns $$ nsenter --net="$server" -- ip link set eth0 up nsenter --net="$server" -- ip link set eth0 multicast on nsenter --net="$server" -- ip addr add "${server_addr}"/24 dev eth0 nsenter --net="$server" -- ip route add default via "${server_addr}" if [ ! -e "$client" ]; then touch "$client" unshare --net="$client" -- ip link set lo up fi nsenter --net="$client" -- sleep 2 & sleep 0.3 ip link set tmp0 netns $! nsenter --net="$client" -- ip link set tmp0 name eth0 nsenter --net="$client" -- ip link set eth0 up nsenter --net="$client" -- ip link set eth0 multicast on nsenter --net="$client" -- ip addr add "${client_addr}"/24 dev eth0 nsenter --net="$client" -- ip route add default via "${client_addr}" nsenter --net="$server" -- ip -br link > "$DIR/tmp" nsenter --net="$server" -- ip -br addr >> "$DIR/tmp" nsenter --net="$server" -- ip -br rout >> "$DIR/tmp" echo "Server" awk '{print " "$0}' "$DIR/tmp" sllip=$(grep "eth0" "$DIR/tmp" | grep -i "fe80" | sed -e 's;.*\(fe80::.*\)/64.*;\1;') if [ -n "$sllip" ] ; then server_addr_ll6=$sllip ; fi nsenter --net="$client" -- ip -br link > "$DIR/tmp" nsenter --net="$client" -- ip -br addr >> "$DIR/tmp" nsenter --net="$client" -- ip -br rout >> "$DIR/tmp" echo "Client" awk '{print " "$0}' "$DIR/tmp" sllip=$(grep "eth0" "$DIR/tmp" | grep -i "fe80" | sed -e 's;.*\(fe80::.*\)/64.*;\1;') if [ -n "$sllip" ] ; then client_addr_ll6=$sllip ; fi # Wait *dad_transmits +1 seconds for DAD to finish and link local address become valid to=$(nsenter --net="$client" -- cat /proc/sys/net/ipv6/conf/eth0/dad_transmits) while [ $to -gt -1 ] ; do nsenter --net="$client" -- ip -6 addr show dev eth0 | grep -q tentative || break : $((to -= 1)) sleep 1 done echo "$server" >> "$DIR/mounts" echo "$client" >> "$DIR/mounts" print "Verifying IPv4 connectivity ..." nsenter --net="$client" -- ping -c1 "${server_addr}" || FAIL "No IPv4 connectivity" print "Verifying IPv6 connectivity ..." nsenter --net="$client" -- ping6 -c 1 ${server_addr_ll6} || FAIL "No IPv6 connectivity" } topo_teardown() { if [ -z "$NM" ]; then echo "NM variable unset, skippnig teardown" exit 1 fi mdnsd_stop # shellcheck disable=SC2162 if [ -f "${DIR}/pids" ]; then while read pid prog; do kill -9 "$pid" 2>/dev/null done < "${DIR}/pids" fi if [ -n "$KEEP_TEST_DATA" ]; then return fi # shellcheck disable=SC2162 if [ -f "${DIR}/mounts" ]; then while read ln; do echo "Unmounting $ln and removing ..." umount "$ln" rm -f "$ln" done < "${DIR}/mounts" fi # NOTE: Uncomment the following two lines for debugging tests # echo "Resulting files in ${DIR}" # return; rm -rf "${DIR}" if [ -n "$TMPSESS" ] && [ -d "$SESSION" ]; then rm -rf "$SESSION" fi } signal() { echo if [ "$1" != "EXIT" ]; then print "Got signal, cleaning up" fi topo_teardown } # props to https://stackoverflow.com/a/2183063/1708249 # shellcheck disable=SC2064 trapit() { func="$1" ; shift for sig ; do trap "$func $sig" "$sig" done } topo() { if [ $# -lt 1 ]; then print "Too few arguments to topo()" exit 1 fi t=$1 shift print "Creating world ..." case "$t" in basic) topo_basic ;; teardown) topo_teardown ;; *) print "No such topology: $t" exit 1 ;; esac } # Runs once when including lib.sh mkdir -p "${DIR}" touch "${DIR}/pids" touch "${DIR}/mounts" trapit signal INT TERM QUIT EXIT mdnsd-0.12/test/lostif.sh000077500000000000000000000007371436331001000153550ustar00rootroot00000000000000#!/bin/sh # Verify mdnsd does not crash when an interface is lost (removed). #set -x # shellcheck source=/dev/null . "$(dirname "$0")/lib.sh" topo basic mdnsd discover print "Deleting eth0 interface ..." # shellcheck disable=SC2154 nsenter --net="$server" -- ip link del eth0 sleep 5 # Verify we don't segfault on loss of interface, issue #74 pgrep mdnsd || FAIL print "Restoring eth0 ..." topo basic sleep 10 print "Rechecking mDNS connectivity" pgrep mdnsd discover OK mdnsd-0.12/test/src/000077500000000000000000000000001436331001000142765ustar00rootroot00000000000000mdnsd-0.12/test/src/.gitignore000066400000000000000000000000501436331001000162610ustar00rootroot00000000000000*~ *.o *.log *.trs .deps .libs xht addr mdnsd-0.12/test/src/Makefile.am000066400000000000000000000013751436331001000163400ustar00rootroot00000000000000AUTOMAKE_OPTIONS = subdir-objects AM_CPPFLAGS = -I$(top_srcdir) AM_LDADD = ../../libmdnsd/libmdnsd.la $(cmocka_LIBS) $(LIBOBJS) # Add source of new files to be in tarball here EXTRA_DIST = unittest.h xht.c addr_test.c # Add new test programs here noinst_PROGRAMS = addr xht addr_SOURCES = addr_test.c addr_CPPFLAGS = $(AM_CPPFLAGS) addr_LDADD = $(AM_LDADD) ../../src/addr.o addr_LDFLAGS = $(AM_LDFLAGS) -Wl,--wrap=getifaddrs -Wl,--wrap=freeifaddrs xht_SOURCES = xht.c xht_CPPFLAGS = $(AM_CPPFLAGS) xht_LDADD = $(AM_LDADD) xht_LDFLAGS = $(AM_LDFLAGS) TESTS = xht TESTS += addr mdnsd-0.12/test/src/addr_test.c000066400000000000000000000342551436331001000164240ustar00rootroot00000000000000#include "unittest.h" #include #include #include #include #include "../../src/mdnsd.h" /* * Mock-ups */ int __wrap_getifaddrs(struct ifaddrs **ifap) { *ifap = mock_ptr_type(struct ifaddrs *); return mock_type(int); } void __wrap_freeifaddrs(struct ifaddrs *ifa) { /* Nothing allocated, nothing to free. */ } struct sockaddr_in ipv4_10_0_20_1 = { AF_INET, 0, { 0x0114000a } }; struct sockaddr_in ipv4_LL_169_254_100_32 = { AF_INET, 0, { 0x2064fea9 } }; struct sockaddr_in ipv4_255_255_255_0 = { AF_INET, 0, { 0x00ffffff } }; struct sockaddr_in ipv4_192_168_2_100 = { AF_INET, 0, { 0x6402a8c0 } }; struct sockaddr_in6 ipv6_global = { AF_INET6, 0, 0, { 0x20,0x01, 0x0d,0xb8, 0x00,0x61, 0xfe,0x01, 0x00,0x00, 0x00,0x00, 0xca,0xfe, 0xba,0xbe}, 0 }; struct sockaddr_in6 ipv6_unique_local = { AF_INET6, 0, 0, { 0xfc,0x00, 0x01,0xaa, 0xbb,0xcc, 0xfe,0x01, 0x00,0x00, 0x00,0x00, 0xca,0xfe, 0xba,0xbe}, 0 }; struct sockaddr_in6 ipv6_site_local = { AF_INET6, 0, 0, { 0xfe,0xc0, 0x51,0xac, 0xb0,0x0b, 0x52,0x01, 0x00,0x00, 0x00,0x00, 0xca,0xfe, 0xba,0xbe}, 0 }; struct sockaddr_in6 ipv6_link_local = { AF_INET6, 0, 0, { 0xfe,0x80, 0x11,0xac, 0xb0,0x0b, 0x52,0x01, 0x00,0x00, 0x00,0x00, 0xca,0xfe, 0xba,0xbe}, 0 }; struct sockaddr_in6 ipv6_FF = { AF_INET6, 0, 0, { 0xff,0xff, 0xff,0xff, 0xff,0xff, 0xff,0xff, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00}, 0 }; struct sockaddr_in6 ipv6_global_2 = { AF_INET6, 0, 0, { 0x20,0x01, 0x0d,0xb8, 0x00,0x00, 0xfe,0x02, 0x00,0x00, 0x00,0x00, 0xde,0xad, 0xc0,0xce}, 0 }; /* * Tests */ /* * Test behaviour when no interfaces exist. */ static void test_iface_init_no_ifc(__attribute__((__unused__)) void **state) { will_return(__wrap_getifaddrs, NULL); will_return(__wrap_getifaddrs, 0); iface_init(NULL); struct iface *iface = iface_iterator(1); assert_null(iface); } /* Helper function testing that one IPv4 is set on one interface. */ static void check_one_iface_one_global_ipv4(char* ifname) { struct iface *iface = iface_iterator(1); assert_non_null(iface); assert_int_equal(0, iface->unused); assert_int_equal(1, iface->changed); assert_string_equal(ifname, iface->ifname); /* Ignoring the (undeterminable) ifindex. */ assert_int_equal(ipv4_10_0_20_1.sin_addr.s_addr, iface->inaddr.s_addr); assert_int_equal(0x00000000, iface->inaddr_old.s_addr); assert_true(IN6_IS_ADDR_UNSPECIFIED(&iface->in6addr)); assert_true(IN6_IS_ADDR_UNSPECIFIED(&iface->in6addr_old)); assert_int_equal(-1, iface->sd); assert_null(iface->mdns); assert_int_equal(1, iface->hostid); iface = iface_iterator(0); assert_null(iface); } /* * Test that one IPv4 is set. */ static void test_iface_init_one_ifc_ipv4(__attribute__((__unused__)) void **state) { struct ifaddrs addrs = { NULL, "if0c1ip4", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv4_10_0_20_1, (struct sockaddr*)&ipv4_255_255_255_0, NULL, NULL }; will_return(__wrap_getifaddrs, &addrs); will_return(__wrap_getifaddrs, 0); iface_init(NULL); check_one_iface_one_global_ipv4(addrs.ifa_name); } /* * Test that a global IPv4 address is not overwritten with a link local one. * One interface, only IPv4. */ static void test_iface_init_one_ifc_global_ipv4_LL_ipv4(__attribute__((__unused__)) void **state) { struct ifaddrs addrs[] = { { addrs + 1, "if0Gip4LLip4", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv4_10_0_20_1, (struct sockaddr*)&ipv4_255_255_255_0, NULL, NULL }, { NULL, "if0Gip4LLip4", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv4_LL_169_254_100_32, (struct sockaddr*)&ipv4_255_255_255_0, NULL, NULL } }; will_return(__wrap_getifaddrs, addrs); will_return(__wrap_getifaddrs, 0); iface_init(NULL); check_one_iface_one_global_ipv4(addrs->ifa_name); } /* * Test that a link local IPv4 address is overwritten by a global one. * One interface, only IPv4. */ static void test_iface_init_one_ifc_LL_ipv4_global_ipv4(__attribute__((__unused__)) void **state) { struct ifaddrs addrs[] = { { addrs + 1, "if0LLip4Gip4", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv4_LL_169_254_100_32, (struct sockaddr*)&ipv4_255_255_255_0, NULL, NULL }, { NULL, "if0LLip4Gip4", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv4_10_0_20_1, (struct sockaddr*)&ipv4_255_255_255_0, NULL, NULL } }; will_return(__wrap_getifaddrs, addrs); will_return(__wrap_getifaddrs, 0); iface_init(NULL); check_one_iface_one_global_ipv4(addrs->ifa_name); } /* Helper function testing that one IPv6 is set on one interface. */ static void check_one_iface_one_global_ipv6(char* ifname) { struct iface *iface = iface_iterator(1); assert_non_null(iface); assert_int_equal(0, iface->unused); assert_int_equal(1, iface->changed); assert_string_equal(ifname, iface->ifname); /* Ignoring the (undeterminable) ifindex. */ assert_int_equal(0x00000000, iface->inaddr.s_addr); assert_int_equal(0x00000000, iface->inaddr_old.s_addr); assert_true(IN6_ARE_ADDR_EQUAL(&ipv6_global.sin6_addr, &iface->in6addr)); assert_true(IN6_IS_ADDR_UNSPECIFIED(&iface->in6addr_old)); assert_int_equal(-1, iface->sd); assert_null(iface->mdns); assert_int_equal(1, iface->hostid); iface = iface_iterator(0); assert_null(iface); } /* * Test that one IPv6 is set. */ static void test_iface_init_one_ifc_ipv6(__attribute__((__unused__)) void **state) { struct ifaddrs addrs = { NULL, "if0c1ip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_global, (struct sockaddr*)&ipv6_FF, NULL, NULL }; will_return(__wrap_getifaddrs, &addrs); will_return(__wrap_getifaddrs, 0); iface_init(NULL); check_one_iface_one_global_ipv6(addrs.ifa_name); } /* * Test that a global IPv6 address is not overwritten with a link local one. * One interface, only IPv6. */ static void test_iface_init_one_ifc_global_ipv6_LL_ipv6(__attribute__((__unused__)) void **state) { struct ifaddrs addrs[] = { { addrs + 1, "if0Gip6LLip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_global, (struct sockaddr*)&ipv6_FF, NULL, NULL }, { NULL, "if0Gip6LLip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_link_local, (struct sockaddr*)&ipv6_FF, NULL, NULL } }; will_return(__wrap_getifaddrs, addrs); will_return(__wrap_getifaddrs, 0); iface_init(NULL); check_one_iface_one_global_ipv6(addrs->ifa_name); } /* * Test that a link local IPv6 address is overwritten by a global one. * One interface, only IPv6. */ static void test_iface_init_one_ifc_LL_ipv6_global_ipv6(__attribute__((__unused__)) void **state) { struct ifaddrs addrs[] = { { addrs + 1, "if0LLip6Gip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_link_local, (struct sockaddr*)&ipv6_FF, NULL, NULL }, { NULL, "if0LLip6Gip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_global, (struct sockaddr*)&ipv6_FF, NULL, NULL } }; will_return(__wrap_getifaddrs, addrs); will_return(__wrap_getifaddrs, 0); iface_init(NULL); check_one_iface_one_global_ipv6(addrs->ifa_name); } /* * Test that IPv6 addresses of lower preference get overwritten by addresses with higher one. * null < link local < site local < unique local < global * One interface, only IPv6. */ static void test_iface_init_one_ifc_LL_SL_UL_global_ipv6(__attribute__((__unused__)) void **state) { struct ifaddrs addrs[] = { { addrs + 1, "if0LLSLULGip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_link_local, (struct sockaddr*)&ipv6_FF, NULL, NULL }, { addrs + 2, "if0LLSLULGip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_site_local, (struct sockaddr*)&ipv6_FF, NULL, NULL }, { addrs + 3, "if0LLSLULGip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_unique_local, (struct sockaddr*)&ipv6_FF, NULL, NULL }, { NULL, "if0LLSLULGip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_global, (struct sockaddr*)&ipv6_FF, NULL, NULL } }; will_return(__wrap_getifaddrs, addrs); will_return(__wrap_getifaddrs, 0); iface_init(NULL); check_one_iface_one_global_ipv6(addrs->ifa_name); } /* * Test that IPv6 addresses of higher preference do not get overwritten by addresses with lower one. * null < link local < site local < unique local < global * One interface, only IPv6. */ static void test_iface_init_one_ifc_global_UL_SL_LL_ipv6(__attribute__((__unused__)) void **state) { struct ifaddrs addrs[] = { { addrs + 1, "if0GULSLLLip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_global, (struct sockaddr*)&ipv6_FF, NULL, NULL }, { addrs + 2, "if0GULSLLLip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_unique_local, (struct sockaddr*)&ipv6_FF, NULL, NULL }, { addrs + 3, "if0GULSLLLip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_site_local, (struct sockaddr*)&ipv6_FF, NULL, NULL }, { NULL, "if0GULSLLLip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_link_local, (struct sockaddr*)&ipv6_FF, NULL, NULL } }; will_return(__wrap_getifaddrs, addrs); will_return(__wrap_getifaddrs, 0); iface_init(NULL); check_one_iface_one_global_ipv6(addrs->ifa_name); } /* Helper function testing that one IPv4 and one IPv6 is set on one interface. */ static void check_one_iface_global_ipv4_ipv6(char* ifname) { struct iface *iface = iface_iterator(1); assert_non_null(iface); assert_int_equal(0, iface->unused); assert_int_equal(1, iface->changed); assert_string_equal(ifname, iface->ifname); /* Ignoring the (undeterminable) ifindex. */ assert_int_equal(ipv4_10_0_20_1.sin_addr.s_addr, iface->inaddr.s_addr); assert_int_equal(0x00000000, iface->inaddr_old.s_addr); assert_true(IN6_ARE_ADDR_EQUAL(&ipv6_global.sin6_addr, &iface->in6addr)); assert_true(IN6_IS_ADDR_UNSPECIFIED(&iface->in6addr_old)); assert_int_equal(-1, iface->sd); assert_null(iface->mdns); assert_int_equal(1, iface->hostid); iface = iface_iterator(0); assert_null(iface); } /* * Test that one IPv4 and one IPv6 is set. * One interface, IPv4 and IPv6 */ static void test_iface_init_one_ifc_ipv4_ll_global_ipv6(__attribute__((__unused__)) void **state) { struct ifaddrs addrs[] = { { addrs +1, "if0c2ip4llgip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv4_10_0_20_1, (struct sockaddr*)&ipv4_255_255_255_0, NULL, NULL }, { addrs + 2, "if0c2ip4llgip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_link_local, (struct sockaddr*)&ipv6_FF, NULL, NULL }, { NULL, "if0c2ip4llgip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_global, (struct sockaddr*)&ipv6_FF, NULL, NULL } }; will_return(__wrap_getifaddrs, &addrs); will_return(__wrap_getifaddrs, 0); iface_init(NULL); check_one_iface_global_ipv4_ipv6(addrs->ifa_name); } /* * Test with two interfaces, IPv4 and IPv6 */ static void test_iface_init_two_ifc_ipv4_ll_global_ipv6_ipv4_ipv6(__attribute__((__unused__)) void **state) { struct ifaddrs addrs[] = { { addrs + 1, "if0c3ip4llgip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv4_10_0_20_1, (struct sockaddr*)&ipv4_255_255_255_0, NULL, NULL }, { addrs + 2, "if1c2ip4gip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv4_192_168_2_100, (struct sockaddr*)&ipv4_255_255_255_0, NULL, NULL }, { addrs + 3, "if0c3ip4llgip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_link_local, (struct sockaddr*)&ipv6_FF, NULL, NULL }, { addrs + 4, "if0c3ip4llgip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_link_local, (struct sockaddr*)&ipv6_FF, NULL, NULL }, { NULL, "if1c2ip4gip6", IFF_UP | IFF_BROADCAST | IFF_MULTICAST, (struct sockaddr*)&ipv6_global_2, (struct sockaddr*)&ipv6_FF, NULL, NULL } }; will_return(__wrap_getifaddrs, &addrs); will_return(__wrap_getifaddrs, 0); iface_init(NULL); check_one_iface_global_ipv4_ipv6(addrs->ifa_name); struct iface *iface = iface_iterator(1); assert_non_null(iface); assert_int_equal(0, iface->unused); assert_int_equal(1, iface->changed); assert_string_equal(addrs[0].ifa_name, iface->ifname); /* Ignoring the (undeterminable) ifindex. */ assert_int_equal(ipv4_10_0_20_1.sin_addr.s_addr, iface->inaddr.s_addr); assert_int_equal(0x00000000, iface->inaddr_old.s_addr); assert_true(IN6_ARE_ADDR_EQUAL(&ipv6_global.sin6_addr, &iface->in6addr)); assert_true(IN6_IS_ADDR_UNSPECIFIED(&iface->in6addr_old)); assert_int_equal(-1, iface->sd); assert_null(iface->mdns); assert_int_equal(1, iface->hostid); iface = iface_iterator(0); assert_non_null(iface); assert_int_equal(0, iface->unused); assert_int_equal(1, iface->changed); assert_string_equal(addrs[1].ifa_name, iface->ifname); /* Ignoring the (undeterminable) ifindex. */ assert_int_equal(ipv4_192_168_2_100.sin_addr.s_addr, iface->inaddr.s_addr); assert_int_equal(0x00000000, iface->inaddr_old.s_addr); assert_true(IN6_ARE_ADDR_EQUAL(&ipv6_global_2.sin6_addr, &iface->in6addr)); assert_true(IN6_IS_ADDR_UNSPECIFIED(&iface->in6addr_old)); assert_int_equal(-1, iface->sd); assert_null(iface->mdns); assert_int_equal(1, iface->hostid); iface = iface_iterator(0); assert_null(iface); } static int teardown(__attribute__((__unused__)) void **state) { iface_exit(); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_iface_init_no_ifc), cmocka_unit_test_teardown(test_iface_init_one_ifc_ipv4, teardown), cmocka_unit_test_teardown(test_iface_init_one_ifc_global_ipv4_LL_ipv4, teardown), cmocka_unit_test_teardown(test_iface_init_one_ifc_LL_ipv4_global_ipv4, teardown), cmocka_unit_test_teardown(test_iface_init_one_ifc_ipv6, teardown), cmocka_unit_test_teardown(test_iface_init_one_ifc_global_ipv6_LL_ipv6, teardown), cmocka_unit_test_teardown(test_iface_init_one_ifc_LL_ipv6_global_ipv6, teardown), cmocka_unit_test_teardown(test_iface_init_one_ifc_LL_SL_UL_global_ipv6, teardown), cmocka_unit_test_teardown(test_iface_init_one_ifc_global_UL_SL_LL_ipv6, teardown), cmocka_unit_test_teardown(test_iface_init_one_ifc_ipv4_ll_global_ipv6, teardown), }; return cmocka_run_group_tests(tests, NULL, NULL); } mdnsd-0.12/test/src/unittest.h000066400000000000000000000001601436331001000163230ustar00rootroot00000000000000#include #include #include #include #include "../../libmdnsd/xht.h" mdnsd-0.12/test/src/xht.c000066400000000000000000000004311436331001000152430ustar00rootroot00000000000000#include "unittest.h" void test1() { xht_t *ptr; ptr = xht_new(1); assert_non_null(ptr); xht_free(ptr); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test1), // cmocka_unit_test(test2), }; return cmocka_run_group_tests(tests, NULL, NULL); }