├── .gitignore ├── src ├── announce.initscript ├── Makefile ├── LICENSE.txt ├── announce.c ├── tinysvcmdns.h └── tinysvcmdns.c ├── Makefile ├── deploy.sh ├── .travis.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | 4 | destination_directory/IPKG* 5 | ._* -------------------------------------------------------------------------------- /src/announce.initscript: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | 3 | START=94 4 | STOP=1 5 | 6 | USE_PROCD=1 7 | 8 | # According to http://wiki.openwrt.org/inbox/procd-init-scripts 9 | # there is not (currently, r41147) support for grabbing 10 | # the PID of a procd service, or sending a signal to that 11 | # service in the reload routine, but it should be possible soon 12 | 13 | service_triggers() { 14 | procd_add_reload_trigger "system" 15 | } 16 | 17 | start_service() { 18 | procd_open_instance 19 | procd_set_param command /usr/sbin/announce 20 | procd_set_param respawn 21 | procd_close_instance 22 | } 23 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile for tinysvcmdns 3 | # 4 | 5 | CFLAGS += -Wall -pedantic -std=gnu99 6 | #CFLAGS += -g 7 | CFLAGS += -O2 -DNDEBUG 8 | LDLIBS = -lpthread 9 | 10 | ifneq ($(CROSS_COMPILE),) 11 | CC = gcc 12 | CC := $(CROSS_COMPILE)$(CC) 13 | AR := $(CROSS_COMPILE)$(AR) 14 | endif 15 | 16 | BIN=announce 17 | 18 | LIBTINYSVCMDNS_OBJS = tinysvcmdns.o 19 | 20 | .PHONY: all clean 21 | 22 | all: $(BIN) libtinysvcmdns.a 23 | 24 | clean: 25 | -$(RM) *.o 26 | -$(RM) *.bin 27 | -$(RM) $(BIN) 28 | -$(RM) libtinysvcmdns.a 29 | 30 | tinysvcmdns: tinysvcmdns.o 31 | 32 | announce: announce.o libtinysvcmdns.a 33 | 34 | libtinysvcmdns.a: $(patsubst %, libtinysvcmdns.a(%), $(LIBTINYSVCMDNS_OBJS)) 35 | 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # To build, pull the announce repository into the "package/" subdirectory in the OpenWrt SDK, 3 | # then run "scripts/feeds install libpthread" and finally "make V=s". 4 | # This will create ./bin/ar71xx/packages/base/announce_1.0_ar71xx.ipk (depending on your architecture) 5 | # 6 | 7 | include $(TOPDIR)/rules.mk 8 | 9 | PKG_NAME:=announce 10 | PKG_VERSION:=1.0 11 | PKG_RELEASE:=1 12 | 13 | PKG_BUILD_PARALLEL:=1 14 | 15 | include $(INCLUDE_DIR)/package.mk 16 | 17 | PKG_BUILD_DEPENDS:= +libpthread 18 | 19 | define Package/announce 20 | SUBMENU:=Utilities 21 | SECTION:=utils 22 | CATEGORY:=Utilities 23 | TITLE:=Announce services on the network with Zeroconf/Bonjour 24 | URL:=https://github.com/probonopd/announce 25 | DEPENDS:= +libpthread 26 | endef 27 | 28 | define Package/announce/description 29 | Announce services on the network with Zeroconf/Bonjour. 30 | This announces services such as ssh, sftp, and http running on the local machine 31 | to the network. 32 | endef 33 | 34 | define Build/Prepare 35 | mkdir -p $(PKG_BUILD_DIR) 36 | $(CP) ./src/* $(PKG_BUILD_DIR)/ 37 | endef 38 | 39 | define Build/Configure 40 | endef 41 | 42 | define Package/announce/install 43 | $(INSTALL_DIR) $(1)/usr/sbin 44 | $(INSTALL_BIN) $(PKG_BUILD_DIR)/announce $(1)/usr/sbin/ 45 | $(INSTALL_DIR) $(1)/etc/init.d 46 | $(INSTALL_BIN) $(PKG_BUILD_DIR)/announce.initscript $(1)/etc/init.d/announce 47 | endef 48 | 49 | $(eval $(call BuildPackage,announce)) 50 | -------------------------------------------------------------------------------- /src/LICENSE.txt: -------------------------------------------------------------------------------- 1 | tinysvcmdns 2 | Copyright (c) 2011, Darell Tan 3 | 4 | announce 5 | Copyright (c) 2014, Simon Peter 6 | 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions 11 | are met: 12 | 1. Redistributions of source code must retain the above copyright 13 | notice, this list of conditions and the following disclaimer. 14 | 2. Redistributions in binary form must reproduce the above copyright 15 | notice, this list of conditions and the following disclaimer in the 16 | documentation and/or other materials provided with the distribution. 17 | 3. The name of the author may not be used to endorse or promote products 18 | derived from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 21 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 22 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 23 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 24 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 25 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 29 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Deploy binaries built with travis-ci to GitHub Pages, 5 | # where they can be accessed by OpenWrt opkg directly 6 | # 7 | # Go to GitHub.com -> Settings -> Applications -> Personal Access Tokens  8 | # —> Create new token, and copy it to your clipboard 9 | # travis encrypt TOKEN=123459... -r username/reponame 10 | # travis-encrypt -r [repository slug] -k TOKEN -v [the token you created before] 11 | # 12 | # In .travis.yml, set: 13 | # env: 14 | # global: 15 | # - USER: 16 | # - REPO: 17 | # - PACKAGE: 18 | # - secure: RIbIq8hI153J5trRa........ 19 | # 20 | 21 | cd /tmp/ 22 | 23 | git clone https://${TOKEN}@github.com/${USER}/${REPO}.git --branch gh-pages \ 24 | --single-branch gh-pages > /dev/null 2>&1 || exit 1 # so that the key does not leak to the logs in case of errors 25 | 26 | cd gh-pages || exit 1 27 | git config user.name "Travis CI" 28 | git config user.email "travis@noreply" 29 | 30 | cp $TRAVIS_BUILD_DIR/*ipk . 31 | $TRAVIS_BUILD_DIR/sdk/OpenWrt-SDK-*/scripts/ipkg-make-index.sh . > Packages 32 | gzip -c Packages > Packages.gz 33 | 34 | cat > index.html <
36 | echo "src/gz announce http://${USER}.github.io/${PACKAGE}" >> /etc/opkg.conf
37 | opkg update
38 | opkg install ${PACKAGE}
39 | 
40 | EOF 41 | 42 | DATE=$(date "+%Y-%m-%d") 43 | cat > README.md <> /etc/opkg.conf 52 | opkg update 53 | opkg install ${PACKAGE} 54 | \`\`\` 55 | EOF 56 | 57 | git add -A 58 | git pull 59 | git commit -a -m "Deploy Travis build $TRAVIS_BUILD_NUMBER to gh-pages" 60 | git push -fq origin gh-pages > /dev/null 2>&1 || exit 1 # so that the key does not leak to the logs in case of errors 61 | echo -e "Uploaded files to gh-pages\n" 62 | cd - 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | cache: 3 | bundler: true 4 | directories: 5 | - cache/ 6 | notifications: 7 | email: false 8 | language: c 9 | compiler: gcc 10 | env: 11 | global: 12 | - PACKAGE=announce 13 | - USER=probonopd 14 | - REPO=announce 15 | - secure: "O0RaGjWTnxfra87BOE2aoG3Ez7zrkdTGmUJn+cT7FqXsxWWhUEl1Ip676P5c0kbJKyOLg975rr9dFvgJsAOpMFsLDYHAyfTAw3Ov07O+89tTxSsrIlWWUkrAk9iFLbfjDvp8VUnQZS3DKxnR/652hPnQOvbIRD+1qPMd04LEQms=" 16 | matrix: 17 | - SDK_URL=https://downloads.openwrt.org/barrier_breaker/14.07/ar71xx/generic/OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2.tar.bz2 18 | - SDK_URL=https://downloads.openwrt.org/barrier_breaker/14.07/kirkwood/generic/OpenWrt-SDK-kirkwood-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2.tar.bz2 19 | - SDK_URL=https://downloads.openwrt.org/barrier_breaker/14.07/lantiq/xway/OpenWrt-SDK-lantiq-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2.tar.bz2 20 | - SDK_URL=https://downloads.openwrt.org/barrier_breaker/14.07/ramips/rt305x/OpenWrt-SDK-ramips-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2.tar.bz2 21 | install: 22 | - mkdir -p $TRAVIS_BUILD_DIR/local ; cd $TRAVIS_BUILD_DIR/local 23 | - wget "http://us.archive.ubuntu.com/ubuntu/pool/main/c/ccache/ccache_3.1.6-1_amd64.deb" 24 | - dpkg -x *.deb . 25 | - mkdir -p $TRAVIS_BUILD_DIR/cache ; cd $TRAVIS_BUILD_DIR/cache 26 | - wget -c $SDK_URL 27 | - mkdir -p $TRAVIS_BUILD_DIR/sdk ; cd $TRAVIS_BUILD_DIR/sdk 28 | - export FILE=$TRAVIS_BUILD_DIR/cache/$(basename $SDK_URL) 29 | - file $FILE 30 | - tar xjf $FILE 31 | - cd $TRAVIS_BUILD_DIR/sdk/OpenWrt-SDK-* 32 | - mkdir package/$PACKAGE 33 | - ln -s $TRAVIS_BUILD_DIR/Makefile package/$PACKAGE/ 34 | - ln -s $TRAVIS_BUILD_DIR/src package/$PACKAGE/ 35 | script: 36 | - export PATH=$TRAVIS_BUILD_DIR/local/usr/bin:$PATH 37 | - cd $TRAVIS_BUILD_DIR/sdk/OpenWrt-SDK-* 38 | - ./scripts/feeds update packages >/dev/null 39 | - ./scripts/feeds install libpthread 40 | - make V=s 41 | - find $TRAVIS_BUILD_DIR/sdk/OpenWrt-SDK-*/bin/ 42 | - find . -name *.ipk -exec cp {} $TRAVIS_BUILD_DIR \; 43 | - cd $TRAVIS_BUILD_DIR/ 44 | - chmod a+x $TRAVIS_BUILD_DIR/deploy.sh 45 | after_success: $TRAVIS_BUILD_DIR/deploy.sh 46 | before_deploy: 47 | git fetch --tags 48 | deploy: 49 | provider: releases 50 | api_key: 51 | secure: L5yTlbnfbQ1FWuvI/33nv2vSZNj3fAHPdQ2ORNB5lkrzrzdEDX66kLyygLVpIUm0ZeFhlKAujrXFh5w9Gu9v4E3fuhVBZZ13ZDuopyTWmJDTu/xGbb/10WlsixzXZqAgYwvv6XxPQ6CvEUV01lppS1+787bAbJhpJKUYebiD8H8= 52 | skip_cleanup: true 53 | file_glob: true 54 | file: $TRAVIS_BUILD_DIR/*.ipk 55 | on: 56 | tags: true 57 | all_branches: true 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | announce [![Build Status](https://travis-ci.org/probonopd/announce.svg)](https://travis-ci.org/probonopd/announce) 2 | ======== 3 | 4 | Announce services on the network with Zeroconf/Bonjour with minimal overhead 5 | 6 | __NOTE:__ For OpenWrt there is now a dedicated `umdns`: https://openwrt.org/docs/guide-developer/mdns. So this repository is mainly for users who cannot use `umdns`. 7 | 8 | Description 9 | -- 10 | This announces services such as ssh, sftp, and http running on the local machine to the network. Specifically geared toward embedded systems running OpenWrt. It is based on tinysvcmdns. 11 | 12 | Note that this could become obsolete once OpenWrt completes **mdns** and integrates it fully into the system; however, so far it is not well documented and I could not figure out how to use it yet. It also appears not to be finished yet. http://git.openwrt.org/?p=project/mdnsd.git;a=tree; In the meantime, announce does its job for me even though it is very basic. 13 | 14 | Downloading 15 | -- 16 | For trunk, announce is in the official OpenWrt packages feed and can be installed and enabled with `opkg install announce ; /etc/init.d/announce enable ; /etc/init.d/announce start`. 17 | 18 | For barrier breaker, run 19 | ``` 20 | echo "src/gz announce http://probonopd.github.io/announce" >> /etc/opkg.conf 21 | opkg update 22 | opkg install announce 23 | # Enable and start the service with: 24 | /etc/init.d/announce enable 25 | /etc/init.d/announce start 26 | ``` 27 | Check the ```.travis.yml``` file to see how this is compiled on http://travis-ci.org automatically. Please file an issue if you need addional architectures and/or OpenWrt versions. 28 | 29 | Building 30 | -- 31 | To build, pull this repository into the ```package/``` subdirectory in the OpenWrt SDK with ```git clone https://github.com/probonopd/announce.git```, then run ```scripts/feeds install libpthread``` and finally ```make V=s```. This will create ./bin/ar71xx/packages/base/announce_1.0-1_ar71xx.ipk (depending on your architecture). 32 | 33 | Here is a detailed log of how this can be successfully compiled on a 64-bit host on which I do not have root rights: 34 | 35 | ``` 36 | wget https://downloads.openwrt.org/barrier_breaker/14.07/ar71xx/generic/OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2.tar.bz2 37 | tar xfj OpenWrt-SDK-*.tar.bz2 38 | cd OpenWrt-SDK-*/ 39 | 40 | cd package/ 41 | git clone https://github.com/probonopd/announce.git 42 | cd - 43 | 44 | scripts/feeds update packages 45 | 46 | # This is ONLY needed for trunk but NOT for barrier_breaker 47 | ls ./staging_dir/toolchain-*/lib/libpthread.so.0 || scripts/feeds install libpthread 48 | 49 | # The following is ONLY needed for barrier_breaker but NOT for trunk 50 | mkdir -p local ; cd local 51 | wget "http://us.archive.ubuntu.com/ubuntu/pool/main/c/ccache/ccache_3.1.6-1_amd64.deb" 52 | ar x ccache_3.1.6-1_amd64.deb 53 | tar xfz data.tar.gz 54 | cd - 55 | export PATH=/home/irmagic/projects/openwrt_barrier_breaker/OpenWrt-SDK-*/local/usr/bin/:$PATH 56 | 57 | make V=s 58 | ``` 59 | Check the ```.travis.yml``` file to see how this is compiled on http://travis-ci.org automatically. 60 | 61 | TODO 62 | -- 63 | * Find a good way to check which services are running and act accordingly (in a loop) 64 | * See https://github.com/probonopd/announce/issues 65 | Pull requests are welcome. 66 | -------------------------------------------------------------------------------- /src/announce.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on abrasive/shairport/master/mdns_tinysvcmdns.c 3 | * Copyright (c) 2013 Paul Lietar 4 | * Copyright (c) 2015 Simon Peter 5 | * All rights reserved. 6 | * 7 | * Permission is hereby granted, free of charge, to any person 8 | * obtaining a copy of this software and associated documentation 9 | * files (the "Software"), to deal in the Software without 10 | * restriction, including without limitation the rights to use, 11 | * copy, modify, merge, publish, distribute, sublicense, and/or 12 | * sell copies of the Software, and to permit persons to whom the 13 | * Software is furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be 16 | * included in all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | * OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "tinysvcmdns.h" 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | int done = 0; 43 | 44 | static struct mdnsd *svr = NULL; 45 | 46 | void term(int signum) 47 | { 48 | printf("\nReceived SIGTERM, exiting...\n"); 49 | 50 | done = 1; 51 | } 52 | 53 | // Find out the PID of a running process; 0 if it does not exist 54 | int pidof(char *name) { 55 | char line[256]; 56 | char buf[256]; 57 | snprintf(buf, sizeof buf, "pidof '%s'", name); 58 | FILE *cmd = popen(buf, "r"); // TODO: Is this a security hole? 59 | fgets(line, 256, cmd); 60 | pid_t pid = strtoul(line, NULL, 10); 61 | pclose(cmd); 62 | return (int)pid; 63 | } 64 | 65 | int main(int argc, char *argv[]) { 66 | 67 | struct sigaction action; 68 | memset(&action, 0, sizeof(struct sigaction)); 69 | action.sa_handler = term; 70 | sigaction(SIGTERM, &action, NULL); // Catch kill, killall 71 | sigaction(SIGINT, &action, NULL); // Catch Ctrl-C 72 | 73 | struct ifaddrs *ifalist; 74 | struct ifaddrs *ifa; 75 | 76 | svr = mdnsd_start(); 77 | if (svr == NULL) { 78 | printf("tinysvcmdns: mdnsd_start() failed\n"); 79 | return -1; 80 | } 81 | 82 | // room for name + .local + NULL 83 | char hostname[100 + 6]; 84 | gethostname(hostname, 99); 85 | // according to POSIX, this may be truncated without a final NULL ! 86 | hostname[99] = 0; 87 | 88 | // readablename stays without ".local" 89 | char readablename[100 + 6]; 90 | strcpy(readablename, hostname); 91 | printf("-->%s\n", readablename); 92 | 93 | // will not work if the hostname doesn't end in .local 94 | char *hostend = hostname + strlen(hostname); 95 | if ((strlen(hostname) < strlen(".local")) || (strcmp(hostend - 6, ".local")!=0)) { 96 | strcat(hostname, ".local"); 97 | } 98 | 99 | if (getifaddrs(&ifalist) < 0) 100 | { 101 | printf("tinysvcmdns: getifaddrs() failed\n"); 102 | return -1; 103 | } 104 | 105 | ifa = ifalist; 106 | 107 | // Look for an ipv4/ipv6 non-loopback interface to use as the main one. 108 | for (ifa = ifalist; ifa != NULL; ifa = ifa->ifa_next) 109 | { 110 | if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr && 111 | ifa->ifa_addr->sa_family == AF_INET) 112 | { 113 | uint32_t main_ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; 114 | 115 | mdnsd_set_hostname(svr, hostname, main_ip); 116 | break; 117 | } 118 | else if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr && 119 | ifa->ifa_addr->sa_family == AF_INET6) 120 | { 121 | // ipv6 unimplemented 122 | } 123 | } 124 | 125 | if (ifa == NULL) 126 | { 127 | printf("tinysvcmdns: no non-loopback interface found\n"); 128 | return -1; 129 | } 130 | 131 | 132 | // Skip the first one, it was already added by set_hostname 133 | for (ifa = ifa->ifa_next; ifa != NULL; ifa = ifa->ifa_next) 134 | { 135 | if (ifa->ifa_flags & IFF_LOOPBACK) // Skip loop-back interfaces 136 | continue; 137 | 138 | switch (ifa->ifa_addr->sa_family) 139 | { 140 | case AF_INET: { // ipv4 141 | uint32_t ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; 142 | struct rr_entry *a_e = rr_create_a(create_nlabel(hostname), ip); 143 | mdnsd_add_rr(svr, a_e); 144 | } 145 | break; 146 | case AF_INET6: { // ipv6 147 | struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; 148 | struct rr_entry *aaaa_e = rr_create_aaaa(create_nlabel(hostname), addr); 149 | mdnsd_add_rr(svr, aaaa_e); 150 | } 151 | break; 152 | } 153 | } 154 | 155 | freeifaddrs(ifa); 156 | 157 | printf("Announcing services. Press Ctrl-C to exit\n"); 158 | 159 | /* 160 | TODO: 161 | (1) Expand the known services or make configurable 162 | (2) Check the actual ports 163 | (3) Possibly do this in a loop to account for started/stopped services 164 | */ 165 | 166 | if(pidof("uhttpd")) { 167 | printf("* uhttpd is running; assuming port 80\n"); 168 | const char *txt0[] = { "path=/", NULL }; 169 | struct mdns_service *svc0 = mdnsd_register_svc(svr, 170 | readablename, 171 | "_http._tcp.local", 172 | 80, 173 | NULL, 174 | txt0); 175 | mdns_service_destroy(svc0); 176 | } else { 177 | printf("- uhttpd is NOT running\n"); 178 | } 179 | 180 | if(pidof("dropbear")) { 181 | printf("* dropbear is running; assuming port 22\n"); 182 | const char *txt1[] = { "", NULL }; 183 | struct mdns_service *svc1 = mdnsd_register_svc(svr, 184 | readablename, 185 | "_ssh._tcp.local", 186 | 22, 187 | NULL, 188 | txt1); 189 | mdns_service_destroy(svc1); 190 | } else { 191 | printf("- dropbear is NOT running\n"); 192 | } 193 | 194 | if(access("/usr/libexec/sftp-server", F_OK) != -1) { 195 | printf("* /usr/libexec/sftp-server exists; assuming port 22\n"); 196 | const char *txt2[] = { "", NULL }; 197 | struct mdns_service *svc2 = mdnsd_register_svc(svr, 198 | readablename, 199 | "_sftp-ssh._tcp.local", 200 | 22, 201 | NULL, 202 | txt2); 203 | mdns_service_destroy(svc2); 204 | } else { 205 | printf("- /usr/libexec/sftp-server does not exist\n"); 206 | } 207 | 208 | /* 209 | if(access("/sys/bus/usb-serial/drivers/ftdi_sio/ttyUSB0", F_OK) != -1) { 210 | printf("* /sys/bus/usb-serial/drivers/ftdi_sio/ttyUSB0 exists; assuming Arduino\n"); 211 | } else { 212 | printf("- /sys/bus/usb-serial/drivers/ftdi_sio/ttyUSB0 does not exist\n"); 213 | } 214 | */ 215 | 216 | while (!done) { 217 | sleep(1); 218 | } 219 | 220 | mdnsd_stop(svr); 221 | printf("Stopped announcing services.\n"); 222 | return 0; 223 | } 224 | -------------------------------------------------------------------------------- /src/tinysvcmdns.h: -------------------------------------------------------------------------------- 1 | // This file is the concatenation of mdnsd.h and mdns.h 2 | // from tinysvcmdns with minor modifications 3 | // The code was taken from https://bitbucket.org/geekman/tinysvcmdns at revision e34b562 4 | 5 | /* 6 | * tinysvcmdns - a tiny MDNS implementation for publishing services 7 | * Copyright (C) 2011 Darell Tan 8 | * All rights reserved. 9 | * 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions 12 | * are met: 13 | * 1. Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 2. Redistributions in binary form must reproduce the above copyright 16 | * notice, this list of conditions and the following disclaimer in the 17 | * documentation and/or other materials provided with the distribution. 18 | * 3. The name of the author may not be used to endorse or promote products 19 | * derived from this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 22 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 23 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 25 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 26 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 30 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef _TINYSVCMDNS_H 34 | 35 | //******************************************************// 36 | // mdns.h // 37 | //******************************************************// 38 | #include 39 | #include 40 | #include 41 | 42 | #ifdef _WIN32 43 | #include 44 | #else 45 | #include 46 | #endif 47 | 48 | #define MALLOC_ZERO_STRUCT(x, type) \ 49 | x = malloc(sizeof(struct type)); \ 50 | memset(x, 0, sizeof(struct type)); 51 | 52 | #define DECL_MALLOC_ZERO_STRUCT(x, type) \ 53 | struct type * MALLOC_ZERO_STRUCT(x, type) 54 | 55 | 56 | 57 | struct rr_data_srv { 58 | uint16_t priority; 59 | uint16_t weight; 60 | uint16_t port; 61 | uint8_t *target; // host 62 | }; 63 | 64 | struct rr_data_txt { 65 | struct rr_data_txt *next; 66 | uint8_t *txt; 67 | }; 68 | 69 | struct rr_data_nsec { 70 | //uint8_t *name; // same as record 71 | 72 | // NSEC occupies the 47th bit, 5 bytes 73 | //uint8_t bitmap_len; // = 5 74 | uint8_t bitmap[5]; // network order: first byte contains LSB 75 | }; 76 | 77 | struct rr_data_ptr { 78 | uint8_t *name; // NULL if entry is to be used 79 | struct rr_entry *entry; 80 | }; 81 | 82 | struct rr_data_a { 83 | uint32_t addr; 84 | }; 85 | 86 | struct rr_data_aaaa { 87 | struct in6_addr *addr; 88 | }; 89 | 90 | struct rr_entry { 91 | uint8_t *name; 92 | 93 | enum rr_type { 94 | RR_A = 0x01, 95 | RR_PTR = 0x0C, 96 | RR_TXT = 0x10, 97 | RR_AAAA = 0x1C, 98 | RR_SRV = 0x21, 99 | RR_NSEC = 0x2F, 100 | RR_ANY = 0xFF, 101 | } type; 102 | 103 | uint32_t ttl; 104 | 105 | // for use in Questions only 106 | char unicast_query; 107 | 108 | // for use in Answers only 109 | char cache_flush; 110 | 111 | uint16_t rr_class; 112 | 113 | // RR data 114 | union { 115 | struct rr_data_nsec NSEC; 116 | struct rr_data_srv SRV; 117 | struct rr_data_txt TXT; 118 | struct rr_data_ptr PTR; 119 | struct rr_data_a A; 120 | struct rr_data_aaaa AAAA; 121 | } data; 122 | }; 123 | 124 | struct rr_list { 125 | struct rr_entry *e; 126 | struct rr_list *next; 127 | }; 128 | 129 | struct rr_group { 130 | uint8_t *name; 131 | 132 | struct rr_list *rr; 133 | 134 | struct rr_group *next; 135 | }; 136 | 137 | #define MDNS_FLAG_RESP (1 << 15) // Query=0 / Response=1 138 | #define MDNS_FLAG_AA (1 << 10) // Authoritative 139 | #define MDNS_FLAG_TC (1 << 9) // TrunCation 140 | #define MDNS_FLAG_RD (1 << 8) // Recursion Desired 141 | #define MDNS_FLAG_RA (1 << 7) // Recursion Available 142 | #define MDNS_FLAG_Z (1 << 6) // Reserved (zero) 143 | 144 | #define MDNS_FLAG_GET_RCODE(x) (x & 0x0F) 145 | #define MDNS_FLAG_GET_OPCODE(x) ((x >> 11) & 0x0F) 146 | 147 | // gets the PTR target name, either from "name" member or "entry" member 148 | #define MDNS_RR_GET_PTR_NAME(rr) (rr->data.PTR.name != NULL ? rr->data.PTR.name : rr->data.PTR.entry->name) 149 | 150 | struct mdns_pkt { 151 | uint16_t id; // transaction ID 152 | uint16_t flags; 153 | uint16_t num_qn; 154 | uint16_t num_ans_rr; 155 | uint16_t num_auth_rr; 156 | uint16_t num_add_rr; 157 | 158 | struct rr_list *rr_qn; // questions 159 | struct rr_list *rr_ans; // answer RRs 160 | struct rr_list *rr_auth; // authority RRs 161 | struct rr_list *rr_add; // additional RRs 162 | }; 163 | 164 | struct mdns_pkt *mdns_parse_pkt(uint8_t *pkt_buf, size_t pkt_len); 165 | 166 | void mdns_init_reply(struct mdns_pkt *pkt, uint16_t id); 167 | size_t mdns_encode_pkt(struct mdns_pkt *answer, uint8_t *pkt_buf, size_t pkt_len); 168 | 169 | void mdns_pkt_destroy(struct mdns_pkt *p); 170 | void rr_group_destroy(struct rr_group *group); 171 | struct rr_group *rr_group_find(struct rr_group *g, uint8_t *name); 172 | struct rr_entry *rr_entry_find(struct rr_list *rr_list, uint8_t *name, uint16_t type); 173 | struct rr_entry *rr_entry_match(struct rr_list *rr_list, struct rr_entry *entry); 174 | void rr_group_add(struct rr_group **group, struct rr_entry *rr); 175 | 176 | int rr_list_count(struct rr_list *rr); 177 | int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr); 178 | struct rr_entry *rr_list_remove(struct rr_list **rr_head, struct rr_entry *rr); 179 | void rr_list_destroy(struct rr_list *rr, char destroy_items); 180 | 181 | struct rr_entry *rr_create_ptr(uint8_t *name, struct rr_entry *d_rr); 182 | struct rr_entry *rr_create_srv(uint8_t *name, uint16_t port, uint8_t *target); 183 | struct rr_entry *rr_create_aaaa(uint8_t *name, struct in6_addr *addr); 184 | struct rr_entry *rr_create_a(uint8_t *name, uint32_t addr); 185 | struct rr_entry *rr_create(uint8_t *name, enum rr_type type); 186 | void rr_set_nsec(struct rr_entry *rr_nsec, enum rr_type type); 187 | void rr_add_txt(struct rr_entry *rr_txt, const char *txt); 188 | 189 | const char *rr_get_type_name(enum rr_type type); 190 | 191 | uint8_t *create_label(const char *txt); 192 | uint8_t *create_nlabel(const char *name); 193 | char *nlabel_to_str(const uint8_t *name); 194 | uint8_t *dup_label(const uint8_t *label); 195 | uint8_t *dup_nlabel(const uint8_t *n); 196 | uint8_t *join_nlabel(const uint8_t *n1, const uint8_t *n2); 197 | 198 | // compares 2 names 199 | static inline int cmp_nlabel(const uint8_t *L1, const uint8_t *L2) { 200 | return strcmp((char *) L1, (char *) L2); 201 | } 202 | 203 | //******************************************************// 204 | // mdnsd.h // 205 | //******************************************************// 206 | 207 | struct mdnsd; 208 | struct mdns_service; 209 | 210 | // starts a MDNS responder instance 211 | // returns NULL if unsuccessful 212 | struct mdnsd *mdnsd_start(); 213 | 214 | // stops the given MDNS responder instance 215 | void mdnsd_stop(struct mdnsd *s); 216 | 217 | // sets the hostname for the given MDNS responder instance 218 | void mdnsd_set_hostname(struct mdnsd *svr, const char *hostname, uint32_t ip); 219 | 220 | // sets the hostname for the given MDNS responder instance, with an ipv6 address 221 | void mdnsd_set_hostname_v6(struct mdnsd *svr, const char *hostname, struct in6_addr *addr); 222 | 223 | // adds an additional RR 224 | void mdnsd_add_rr(struct mdnsd *svr, struct rr_entry *rr); 225 | 226 | // registers a service with the MDNS responder instance 227 | struct mdns_service *mdnsd_register_svc(struct mdnsd *svr, const char *instance_name, 228 | const char *type, uint16_t port, const char *hostname, const char *txt[]); 229 | 230 | // destroys the mdns_service struct returned by mdnsd_register_svc() 231 | void mdns_service_destroy(struct mdns_service *srv); 232 | 233 | 234 | #endif // _TINYSVCMDNS_H 235 | 236 | -------------------------------------------------------------------------------- /src/tinysvcmdns.c: -------------------------------------------------------------------------------- 1 | // This file is the concatenation of mdnsd.c and mdns.c 2 | // from tinysvcmdns with minor modifications 3 | // The code was taken from 4 | // https://raw.githubusercontent.com/mikebrady/shairport-sync/tinysvcmdns.h 5 | // at revision 44c34f6dab6fb80d08418a9f328c674d6c644fd6 6 | 7 | /* 8 | * tinysvcmdns - a tiny MDNS implementation for publishing services 9 | * Copyright (C) 2011 Darell Tan 10 | * All rights reserved. 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 1. Redistributions of source code must retain the above copyright 16 | * notice, this list of conditions and the following disclaimer. 17 | * 2. Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * 3. The name of the author may not be used to endorse or promote products 21 | * derived from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 24 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 25 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 28 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 32 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | #include "tinysvcmdns.h" 36 | 37 | #define DEBUG_PRINTF(...) debug(1, __VA_ARGS__) 38 | #define log_message(level, ...) \ 39 | do { \ 40 | switch(level) \ 41 | { \ 42 | case LOG_ERR: \ 43 | warn(__VA_ARGS__); \ 44 | break; \ 45 | default: \ 46 | debug(1, __VA_ARGS__); \ 47 | } \ 48 | } while (0) 49 | 50 | 51 | //******************************************************// 52 | // mdns.c // 53 | //******************************************************// 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | 60 | #ifdef _WIN32 61 | #include 62 | #include 63 | #else 64 | #include 65 | #endif 66 | 67 | // See RFC 6762 Section 10 for an account of two TTLs -- 120 seconds for rrs with a host name as the record's name 68 | // or a host name in the record's rdata 69 | // 75 minutes for everything else. 70 | // https://tools.ietf.org/html/rfc6762 71 | 72 | #define DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME 120 73 | #define DEFAULT_TTL 4500 74 | 75 | struct name_comp { 76 | uint8_t *label; // label 77 | size_t pos; // position in msg 78 | 79 | struct name_comp *next; 80 | }; 81 | 82 | // ----- label functions ----- 83 | 84 | // duplicates a name 85 | inline uint8_t *dup_nlabel(const uint8_t *n) { 86 | assert(n[0] <= 63); // prevent mis-use 87 | return (uint8_t *) strdup((char *) n); 88 | } 89 | 90 | // duplicates a label 91 | uint8_t *dup_label(const uint8_t *label) { 92 | int len = *label + 1; 93 | if (len > 63) 94 | return NULL; 95 | uint8_t *newlabel = malloc(len + 1); 96 | strncpy((char *) newlabel, (char *) label, len); 97 | newlabel[len] = '\0'; 98 | return newlabel; 99 | } 100 | 101 | uint8_t *join_nlabel(const uint8_t *n1, const uint8_t *n2) { 102 | int len1, len2; 103 | uint8_t *s; 104 | 105 | assert(n1[0] <= 63 && n2[0] <= 63); // detect misuse 106 | 107 | len1 = strlen((char *) n1); 108 | len2 = strlen((char *) n2); 109 | 110 | s = malloc(len1 + len2 + 1); 111 | strncpy((char *) s, (char *) n1, len1); 112 | strncpy((char *) s+len1, (char *) n2, len2); 113 | s[len1 + len2] = '\0'; 114 | return s; 115 | } 116 | 117 | // returns a human-readable name label in dotted form 118 | char *nlabel_to_str(const uint8_t *name) { 119 | char *label, *labelp; 120 | const uint8_t *p; 121 | 122 | assert(name != NULL); 123 | 124 | label = labelp = malloc(256); 125 | 126 | for (p = name; *p; p++) { 127 | strncpy(labelp, (char *) p + 1, *p); 128 | labelp += *p; 129 | *labelp = '.'; 130 | labelp++; 131 | 132 | p += *p; 133 | } 134 | 135 | *labelp = '\0'; 136 | 137 | return label; 138 | } 139 | 140 | // returns the length of a label field 141 | // does NOT uncompress the field, so it could be as small as 2 bytes 142 | // or 1 for the root 143 | static size_t label_len(uint8_t *pkt_buf, size_t pkt_len, size_t off) { 144 | uint8_t *p; 145 | uint8_t *e = pkt_buf + pkt_len; 146 | size_t len = 0; 147 | 148 | for (p = pkt_buf + off; p < e; p++) { 149 | if (*p == 0) { 150 | return len + 1; 151 | } else if ((*p & 0xC0) == 0xC0) { 152 | return len + 2; 153 | } else { 154 | len += *p + 1; 155 | p += *p; 156 | } 157 | } 158 | 159 | return len; 160 | } 161 | 162 | // creates a label 163 | // free() after use 164 | uint8_t *create_label(const char *txt) { 165 | int len; 166 | uint8_t *s; 167 | 168 | assert(txt != NULL); 169 | len = strlen(txt); 170 | if (len > 63) 171 | return NULL; 172 | 173 | s = malloc(len + 2); 174 | s[0] = len; 175 | strncpy((char *) s + 1, txt, len); 176 | s[len + 1] = '\0'; 177 | 178 | return s; 179 | } 180 | 181 | // creates a uncompressed name label given a DNS name like "apple.b.com" 182 | // free() after use 183 | uint8_t *create_nlabel(const char *name) { 184 | char *label; 185 | char *p, *e, *lenpos; 186 | int len = 0; 187 | 188 | assert(name != NULL); 189 | 190 | len = strlen(name); 191 | label = malloc(len + 1 + 1); 192 | if (label == NULL) 193 | return NULL; 194 | 195 | strncpy((char *) label + 1, name, len); 196 | label[len + 1] = '\0'; 197 | 198 | p = label; 199 | e = p + len; 200 | lenpos = p; 201 | 202 | while (p < e) { 203 | *lenpos = 0; 204 | char *dot = memchr(p + 1, '.', e - p - 1); 205 | if (dot == NULL) 206 | dot = e + 1; 207 | *lenpos = dot - p - 1; 208 | 209 | p = dot; 210 | lenpos = dot; 211 | } 212 | 213 | return (uint8_t *) label; 214 | } 215 | 216 | // copies a label from the buffer into a newly-allocated string 217 | // free() after use 218 | static uint8_t *copy_label(uint8_t *pkt_buf, size_t pkt_len, size_t off) { 219 | int len; 220 | 221 | if (off > pkt_len) 222 | return NULL; 223 | 224 | len = pkt_buf[off] + 1; 225 | if (off + len > pkt_len) { 226 | // DEBUG_PRINTF("label length exceeds packet buffer\n"); 227 | return NULL; 228 | } 229 | 230 | return dup_label(pkt_buf + off); 231 | } 232 | 233 | // uncompresses a name 234 | // free() after use 235 | static uint8_t *uncompress_nlabel(uint8_t *pkt_buf, size_t pkt_len, size_t off) { 236 | uint8_t *p; 237 | uint8_t *e = pkt_buf + pkt_len; 238 | size_t len = 0; 239 | char *str, *sp; 240 | if (off >= pkt_len) 241 | return NULL; 242 | 243 | // calculate length of uncompressed label 244 | for (p = pkt_buf + off; *p && p < e; p++) { 245 | size_t llen = 0; 246 | if ((*p & 0xC0) == 0xC0) { 247 | uint8_t *p2 = pkt_buf + (((p[0] & ~0xC0) << 8) | p[1]); 248 | llen = *p2 + 1; 249 | p = p2 + llen - 1; 250 | } else { 251 | llen = *p + 1; 252 | p += llen - 1; 253 | } 254 | len += llen; 255 | } 256 | 257 | str = sp = malloc(len + 1); 258 | if (str == NULL) 259 | return NULL; 260 | 261 | // FIXME: must merge this with above code 262 | for (p = pkt_buf + off; *p && p < e; p++) { 263 | size_t llen = 0; 264 | if ((*p & 0xC0) == 0xC0) { 265 | uint8_t *p2 = pkt_buf + (((p[0] & ~0xC0) << 8) | p[1]); 266 | llen = *p2 + 1; 267 | strncpy(sp, (char *) p2, llen); 268 | p = p2 + llen - 1; 269 | } else { 270 | llen = *p + 1; 271 | strncpy(sp, (char *) p, llen); 272 | p += llen - 1; 273 | } 274 | sp += llen; 275 | } 276 | *sp = '\0'; 277 | 278 | return (uint8_t *) str; 279 | } 280 | 281 | // ----- RR list & group functions ----- 282 | 283 | const char *rr_get_type_name(enum rr_type type) { 284 | switch (type) { 285 | case RR_A: return "A"; 286 | case RR_PTR: return "PTR"; 287 | case RR_TXT: return "TXT"; 288 | case RR_AAAA: return "AAAA"; 289 | case RR_SRV: return "SRV"; 290 | case RR_NSEC: return "NSEC"; 291 | case RR_ANY: return "ANY"; 292 | } 293 | return NULL; 294 | } 295 | 296 | void rr_entry_destroy(struct rr_entry *rr) { 297 | struct rr_data_txt *txt_rec; 298 | assert(rr); 299 | 300 | // check rr_type and free data elements 301 | switch (rr->type) { 302 | case RR_PTR: 303 | if (rr->data.PTR.name) 304 | free(rr->data.PTR.name); 305 | // don't free entry 306 | break; 307 | 308 | case RR_TXT: 309 | txt_rec = &rr->data.TXT; 310 | while (txt_rec) { 311 | struct rr_data_txt *next = txt_rec->next; 312 | if (txt_rec->txt) 313 | free(txt_rec->txt); 314 | 315 | // only free() if it wasn't part of the struct 316 | if (txt_rec != &rr->data.TXT) 317 | free(txt_rec); 318 | 319 | txt_rec = next; 320 | } 321 | break; 322 | 323 | case RR_SRV: 324 | if (rr->data.SRV.target) 325 | free(rr->data.SRV.target); 326 | break; 327 | 328 | default: 329 | // nothing to free 330 | break; 331 | } 332 | 333 | free(rr->name); 334 | free(rr); 335 | } 336 | 337 | // destroys an RR list (and optionally, items) 338 | void rr_list_destroy(struct rr_list *rr, char destroy_items) { 339 | struct rr_list *rr_next; 340 | 341 | for (; rr; rr = rr_next) { 342 | rr_next = rr->next; 343 | if (destroy_items) 344 | rr_entry_destroy(rr->e); 345 | free(rr); 346 | } 347 | } 348 | 349 | int rr_list_count(struct rr_list *rr) { 350 | int i = 0; 351 | for (; rr; i++, rr = rr->next); 352 | return i; 353 | } 354 | 355 | struct rr_entry *rr_list_remove(struct rr_list **rr_head, struct rr_entry *rr) { 356 | struct rr_list *le = *rr_head, *pe = NULL; 357 | for (; le; le = le->next) { 358 | if (le->e == rr) { 359 | if (pe == NULL) { 360 | *rr_head = le->next; 361 | free(le); 362 | return rr; 363 | } else { 364 | pe->next = le->next; 365 | free(le); 366 | return rr; 367 | } 368 | } 369 | pe = le; 370 | } 371 | return NULL; 372 | } 373 | 374 | // appends an rr_entry to an RR list 375 | // if the RR is already in the list, it will not be added 376 | // RRs are compared by memory location - not its contents 377 | // return value of 0 means item not added 378 | int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr) { 379 | struct rr_list *node = malloc(sizeof(struct rr_list)); 380 | node->e = rr; 381 | node->next = NULL; 382 | 383 | if (*rr_head == NULL) { 384 | *rr_head = node; 385 | } else { 386 | struct rr_list *e = *rr_head, *taile; 387 | for (; e; e = e->next) { 388 | // already in list - don't add 389 | if (e->e == rr) { 390 | free(node); 391 | return 0; 392 | } 393 | if (e->next == NULL) 394 | taile = e; 395 | } 396 | taile->next = node; 397 | } 398 | return 1; 399 | } 400 | 401 | #define FILL_RR_ENTRY(rr, _name, _type) \ 402 | rr->name = _name; \ 403 | rr->type = _type; \ 404 | rr->ttl = DEFAULT_TTL; \ 405 | rr->cache_flush = 1; \ 406 | rr->rr_class = 1; 407 | 408 | struct rr_entry *rr_create_a(uint8_t *name, uint32_t addr) { 409 | DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); 410 | FILL_RR_ENTRY(rr, name, RR_A); 411 | rr->data.A.addr = addr; 412 | rr->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // 120 seconds -- see RFC 6762 Section 10 413 | return rr; 414 | } 415 | 416 | struct rr_entry *rr_create_aaaa(uint8_t *name, struct in6_addr *addr) { 417 | DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); 418 | FILL_RR_ENTRY(rr, name, RR_AAAA); 419 | rr->data.AAAA.addr = addr; 420 | rr->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // 120 seconds -- see RFC 6762 Section 10 421 | return rr; 422 | } 423 | 424 | struct rr_entry *rr_create_srv(uint8_t *name, uint16_t port, uint8_t *target) { 425 | DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); 426 | FILL_RR_ENTRY(rr, name, RR_SRV); 427 | rr->data.SRV.port = port; 428 | rr->data.SRV.target = target; 429 | return rr; 430 | } 431 | 432 | struct rr_entry *rr_create_ptr(uint8_t *name, struct rr_entry *d_rr) { 433 | DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); 434 | FILL_RR_ENTRY(rr, name, RR_PTR); 435 | rr->cache_flush = 0; // PTRs shouldn't have their cache flush bit set 436 | rr->data.PTR.entry = d_rr; 437 | return rr; 438 | } 439 | 440 | struct rr_entry *rr_create(uint8_t *name, enum rr_type type) { 441 | DECL_MALLOC_ZERO_STRUCT(rr, rr_entry); 442 | FILL_RR_ENTRY(rr, name, type); 443 | return rr; 444 | } 445 | 446 | void rr_set_nsec(struct rr_entry *rr_nsec, enum rr_type type) { 447 | assert(rr_nsec->type = RR_NSEC); 448 | assert((type / 8) < sizeof(rr_nsec->data.NSEC.bitmap)); 449 | 450 | rr_nsec->data.NSEC.bitmap[ type / 8 ] = 1 << (7 - (type % 8)); 451 | } 452 | 453 | void rr_add_txt(struct rr_entry *rr_txt, const char *txt) { 454 | struct rr_data_txt *txt_rec; 455 | assert(rr_txt->type == RR_TXT); 456 | 457 | txt_rec = &rr_txt->data.TXT; 458 | 459 | // is current data filled? 460 | if (txt_rec->txt == NULL) { 461 | txt_rec->txt = create_label(txt); 462 | return; 463 | } 464 | 465 | // find the last node 466 | for (; txt_rec->next; txt_rec = txt_rec->next); 467 | 468 | // create a new empty node 469 | txt_rec->next = malloc(sizeof(struct rr_data_txt)); 470 | 471 | txt_rec = txt_rec->next; 472 | txt_rec->txt = create_label(txt); 473 | txt_rec->next = NULL; 474 | } 475 | 476 | // adds a record to an rr_group 477 | void rr_group_add(struct rr_group **group, struct rr_entry *rr) { 478 | struct rr_group *g; 479 | 480 | assert(rr != NULL); 481 | 482 | if (*group) { 483 | g = rr_group_find(*group, rr->name); 484 | if (g) { 485 | rr_list_append(&g->rr, rr); 486 | return; 487 | } 488 | } 489 | 490 | MALLOC_ZERO_STRUCT(g, rr_group); 491 | g->name = dup_nlabel(rr->name); 492 | rr_list_append(&g->rr, rr); 493 | 494 | // prepend to list 495 | g->next = *group; 496 | *group = g; 497 | } 498 | 499 | // finds a rr_group matching the given name 500 | struct rr_group *rr_group_find(struct rr_group* g, uint8_t *name) { 501 | for (; g; g = g->next) { 502 | if (cmp_nlabel(g->name, name) == 0) 503 | return g; 504 | } 505 | return NULL; 506 | } 507 | 508 | struct rr_entry *rr_entry_find(struct rr_list *rr_list, uint8_t *name, uint16_t type) { 509 | struct rr_list *rr = rr_list; 510 | for (; rr; rr = rr->next) { 511 | if (rr->e->type == type && cmp_nlabel(rr->e->name, name) == 0) 512 | return rr->e; 513 | } 514 | return NULL; 515 | } 516 | 517 | // looks for a matching entry in rr_list 518 | // if entry is a PTR, we need to check if the PTR target also matches 519 | struct rr_entry *rr_entry_match(struct rr_list *rr_list, struct rr_entry *entry) { 520 | struct rr_list *rr = rr_list; 521 | for (; rr; rr = rr->next) { 522 | if (rr->e->type == entry->type && cmp_nlabel(rr->e->name, entry->name) == 0) { 523 | if (entry->type != RR_PTR) { 524 | return rr->e; 525 | } else if (cmp_nlabel(MDNS_RR_GET_PTR_NAME(entry), MDNS_RR_GET_PTR_NAME(rr->e)) == 0) { 526 | // if it's a PTR, we need to make sure PTR target also matches 527 | return rr->e; 528 | } 529 | } 530 | } 531 | return NULL; 532 | } 533 | 534 | void rr_group_destroy(struct rr_group *group) { 535 | struct rr_group *g = group; 536 | 537 | while (g) { 538 | struct rr_group *nextg = g->next; 539 | free(g->name); 540 | rr_list_destroy(g->rr, 1); 541 | free(g); 542 | g = nextg; 543 | } 544 | } 545 | 546 | uint8_t *mdns_write_u16(uint8_t *ptr, const uint16_t v) { 547 | *ptr++ = (uint8_t) (v >> 8) & 0xFF; 548 | *ptr++ = (uint8_t) (v >> 0) & 0xFF; 549 | return ptr; 550 | } 551 | 552 | uint8_t *mdns_write_u32(uint8_t *ptr, const uint32_t v) { 553 | *ptr++ = (uint8_t) (v >> 24) & 0xFF; 554 | *ptr++ = (uint8_t) (v >> 16) & 0xFF; 555 | *ptr++ = (uint8_t) (v >> 8) & 0xFF; 556 | *ptr++ = (uint8_t) (v >> 0) & 0xFF; 557 | return ptr; 558 | } 559 | 560 | uint16_t mdns_read_u16(const uint8_t *ptr) { 561 | return ((ptr[0] & 0xFF) << 8) | 562 | ((ptr[1] & 0xFF) << 0); 563 | } 564 | 565 | uint32_t mdns_read_u32(const uint8_t *ptr) { 566 | return ((ptr[0] & 0xFF) << 24) | 567 | ((ptr[1] & 0xFF) << 16) | 568 | ((ptr[2] & 0xFF) << 8) | 569 | ((ptr[3] & 0xFF) << 0); 570 | } 571 | 572 | // initialize the packet for reply 573 | // clears the packet of list structures but not its list items 574 | void mdns_init_reply(struct mdns_pkt *pkt, uint16_t id) { 575 | // copy transaction ID 576 | pkt->id = id; 577 | 578 | // response flags 579 | pkt->flags = MDNS_FLAG_RESP | MDNS_FLAG_AA; 580 | 581 | rr_list_destroy(pkt->rr_qn, 0); 582 | rr_list_destroy(pkt->rr_ans, 0); 583 | rr_list_destroy(pkt->rr_auth, 0); 584 | rr_list_destroy(pkt->rr_add, 0); 585 | 586 | pkt->rr_qn = NULL; 587 | pkt->rr_ans = NULL; 588 | pkt->rr_auth = NULL; 589 | pkt->rr_add = NULL; 590 | 591 | pkt->num_qn = 0; 592 | pkt->num_ans_rr = 0; 593 | pkt->num_auth_rr = 0; 594 | pkt->num_add_rr = 0; 595 | } 596 | 597 | // destroys an mdns_pkt struct, including its contents 598 | void mdns_pkt_destroy(struct mdns_pkt *p) { 599 | rr_list_destroy(p->rr_qn, 1); 600 | rr_list_destroy(p->rr_ans, 1); 601 | rr_list_destroy(p->rr_auth, 1); 602 | rr_list_destroy(p->rr_add, 1); 603 | 604 | free(p); 605 | } 606 | 607 | 608 | // parse the MDNS questions section 609 | // stores the parsed data in the given mdns_pkt struct 610 | static size_t mdns_parse_qn(uint8_t *pkt_buf, size_t pkt_len, size_t off, 611 | struct mdns_pkt *pkt) { 612 | const uint8_t *p = pkt_buf + off; 613 | struct rr_entry *rr; 614 | uint8_t *name; 615 | 616 | assert(pkt != NULL); 617 | 618 | rr = malloc(sizeof(struct rr_entry)); 619 | memset(rr, 0, sizeof(struct rr_entry)); 620 | 621 | name = uncompress_nlabel(pkt_buf, pkt_len, off); 622 | p += label_len(pkt_buf, pkt_len, off); 623 | rr->name = name; 624 | 625 | rr->type = mdns_read_u16(p); 626 | p += sizeof(uint16_t); 627 | 628 | rr->unicast_query = (*p & 0x80) == 0x80; 629 | rr->rr_class = mdns_read_u16(p) & ~0x80; 630 | p += sizeof(uint16_t); 631 | 632 | rr_list_append(&pkt->rr_qn, rr); 633 | 634 | return p - (pkt_buf + off); 635 | } 636 | 637 | // parse the MDNS RR section 638 | // stores the parsed data in the given mdns_pkt struct 639 | static size_t mdns_parse_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, 640 | struct mdns_pkt *pkt) { 641 | const uint8_t *p = pkt_buf + off; 642 | const uint8_t *e = pkt_buf + pkt_len; 643 | struct rr_entry *rr; 644 | uint8_t *name; 645 | size_t rr_data_len = 0; 646 | struct rr_data_txt *txt_rec; 647 | int parse_error = 0; 648 | 649 | assert(pkt != NULL); 650 | 651 | if (off > pkt_len) 652 | return 0; 653 | 654 | rr = malloc(sizeof(struct rr_entry)); 655 | memset(rr, 0, sizeof(struct rr_entry)); 656 | 657 | name = uncompress_nlabel(pkt_buf, pkt_len, off); 658 | p += label_len(pkt_buf, pkt_len, off); 659 | rr->name = name; 660 | 661 | rr->type = mdns_read_u16(p); 662 | p += sizeof(uint16_t); 663 | 664 | rr->cache_flush = (*p & 0x80) == 0x80; 665 | rr->rr_class = mdns_read_u16(p) & ~0x80; 666 | p += sizeof(uint16_t); 667 | 668 | rr->ttl = mdns_read_u32(p); 669 | p += sizeof(uint32_t); 670 | 671 | // RR data 672 | rr_data_len = mdns_read_u16(p); 673 | p += sizeof(uint16_t); 674 | 675 | if (p + rr_data_len > e) { 676 | // DEBUG_PRINTF("rr_data_len goes beyond packet buffer: %lu > %lu\n", rr_data_len, e - p); 677 | rr_entry_destroy(rr); 678 | return 0; 679 | } 680 | 681 | e = p + rr_data_len; 682 | 683 | // see if we can parse the RR data 684 | switch (rr->type) { 685 | case RR_A: 686 | if (rr_data_len < sizeof(uint32_t)) { 687 | // DEBUG_PRINTF("invalid rr_data_len=%lu for A record\n", rr_data_len); 688 | parse_error = 1; 689 | break; 690 | } 691 | rr->data.A.addr = ntohl(mdns_read_u32(p)); /* addr already in net order */ 692 | p += sizeof(uint32_t); 693 | break; 694 | 695 | case RR_AAAA: 696 | if (rr_data_len < sizeof(struct in6_addr)) { 697 | // DEBUG_PRINTF("invalid rr_data_len=%lu for AAAA record\n", rr_data_len); 698 | parse_error = 1; 699 | break; 700 | } 701 | rr->data.AAAA.addr = malloc(sizeof(struct in6_addr)); 702 | int i; 703 | for (i = 0; i < sizeof(struct in6_addr); i++) 704 | rr->data.AAAA.addr->s6_addr[i] = p[i]; 705 | p += sizeof(struct in6_addr); 706 | break; 707 | 708 | case RR_PTR: 709 | rr->data.PTR.name = uncompress_nlabel(pkt_buf, pkt_len, p - pkt_buf); 710 | if (rr->data.PTR.name == NULL) { 711 | // DEBUG_PRINTF("unable to parse/uncompress label for PTR name\n"); 712 | parse_error = 1; 713 | break; 714 | } 715 | p += rr_data_len; 716 | break; 717 | 718 | case RR_TXT: 719 | txt_rec = &rr->data.TXT; 720 | 721 | // not supposed to happen, but we should handle it 722 | if (rr_data_len == 0) { 723 | // DEBUG_PRINTF("WARN: rr_data_len for TXT is 0\n"); 724 | txt_rec->txt = create_label(""); 725 | break; 726 | } 727 | 728 | while (1) { 729 | txt_rec->txt = copy_label(pkt_buf, pkt_len, p - pkt_buf); 730 | if (txt_rec->txt == NULL) { 731 | // DEBUG_PRINTF("unable to copy label for TXT record\n"); 732 | parse_error = 1; 733 | break; 734 | } 735 | p += txt_rec->txt[0] + 1; 736 | 737 | if (p >= e) 738 | break; 739 | 740 | // allocate another record 741 | txt_rec->next = malloc(sizeof(struct rr_data_txt)); 742 | txt_rec = txt_rec->next; 743 | txt_rec->next = NULL; 744 | } 745 | break; 746 | 747 | default: 748 | // skip to end of RR data 749 | p = e; 750 | } 751 | 752 | // if there was a parse error, destroy partial rr_entry 753 | if (parse_error) { 754 | rr_entry_destroy(rr); 755 | return 0; 756 | } 757 | 758 | rr_list_append(&pkt->rr_ans, rr); 759 | 760 | return p - (pkt_buf + off); 761 | } 762 | 763 | // parse a MDNS packet into an mdns_pkt struct 764 | struct mdns_pkt *mdns_parse_pkt(uint8_t *pkt_buf, size_t pkt_len) { 765 | uint8_t *p = pkt_buf; 766 | size_t off; 767 | struct mdns_pkt *pkt; 768 | int i; 769 | 770 | if (pkt_len < 12) 771 | return NULL; 772 | 773 | MALLOC_ZERO_STRUCT(pkt, mdns_pkt); 774 | 775 | // parse header 776 | pkt->id = mdns_read_u16(p); p += sizeof(uint16_t); 777 | pkt->flags = mdns_read_u16(p); p += sizeof(uint16_t); 778 | pkt->num_qn = mdns_read_u16(p); p += sizeof(uint16_t); 779 | pkt->num_ans_rr = mdns_read_u16(p); p += sizeof(uint16_t); 780 | pkt->num_auth_rr = mdns_read_u16(p); p += sizeof(uint16_t); 781 | pkt->num_add_rr = mdns_read_u16(p); p += sizeof(uint16_t); 782 | 783 | off = p - pkt_buf; 784 | 785 | // parse questions 786 | for (i = 0; i < pkt->num_qn; i++) { 787 | size_t l = mdns_parse_qn(pkt_buf, pkt_len, off, pkt); 788 | if (! l) { 789 | // DEBUG_PRINTF("error parsing question #%d\n", i); 790 | mdns_pkt_destroy(pkt); 791 | return NULL; 792 | } 793 | 794 | off += l; 795 | } 796 | 797 | // parse answer RRs 798 | for (i = 0; i < pkt->num_ans_rr; i++) { 799 | size_t l = mdns_parse_rr(pkt_buf, pkt_len, off, pkt); 800 | if (! l) { 801 | // DEBUG_PRINTF("error parsing answer #%d\n", i); 802 | mdns_pkt_destroy(pkt); 803 | return NULL; 804 | } 805 | 806 | off += l; 807 | } 808 | 809 | // TODO: parse the authority and additional RR sections 810 | 811 | return pkt; 812 | } 813 | 814 | // encodes a name (label) into a packet using the name compression scheme 815 | // encoded names will be added to the compression list for subsequent use 816 | static size_t mdns_encode_name(uint8_t *pkt_buf, size_t pkt_len, size_t off, 817 | const uint8_t *name, struct name_comp *comp) { 818 | struct name_comp *c, *c_tail = NULL; 819 | uint8_t *p = pkt_buf + off; 820 | size_t len = 0; 821 | 822 | if (name) { 823 | while (*name) { 824 | // find match for compression 825 | for (c = comp; c; c = c->next) { 826 | if (cmp_nlabel(name, c->label) == 0) { 827 | mdns_write_u16(p, 0xC000 | (c->pos & ~0xC000)); 828 | return len + sizeof(uint16_t); 829 | } 830 | 831 | if (c->next == NULL) 832 | c_tail = c; 833 | } 834 | 835 | // copy this segment 836 | int segment_len = *name + 1; 837 | strncpy((char *) p, (char *) name, segment_len); 838 | 839 | // cache the name for subsequent compression 840 | DECL_MALLOC_ZERO_STRUCT(new_c, name_comp); 841 | 842 | new_c->label = (uint8_t *) name; 843 | new_c->pos = p - pkt_buf; 844 | c_tail->next = new_c; 845 | 846 | // advance to next name segment 847 | p += segment_len; 848 | len += segment_len; 849 | name += segment_len; 850 | } 851 | } 852 | 853 | *p = '\0'; // root "label" 854 | len += 1; 855 | 856 | return len; 857 | } 858 | 859 | // encodes an RR entry at the given offset 860 | // returns the size of the entire RR entry 861 | static size_t mdns_encode_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off, 862 | struct rr_entry *rr, struct name_comp *comp) { 863 | uint8_t *p = pkt_buf + off, *p_data; 864 | size_t l; 865 | struct rr_data_txt *txt_rec; 866 | uint8_t *label; 867 | int i; 868 | 869 | assert(off < pkt_len); 870 | 871 | // name 872 | l = mdns_encode_name(pkt_buf, pkt_len, off, rr->name, comp); 873 | assert(l != 0); 874 | p += l; 875 | 876 | // type 877 | p = mdns_write_u16(p, rr->type); 878 | 879 | // class & cache flush 880 | p = mdns_write_u16(p, (rr->rr_class & ~0x8000) | (rr->cache_flush << 15)); 881 | 882 | // TTL 883 | p = mdns_write_u32(p, rr->ttl); 884 | 885 | // data length (filled in later) 886 | p += sizeof(uint16_t); 887 | 888 | // start of data marker 889 | p_data = p; 890 | 891 | switch (rr->type) { 892 | case RR_A: 893 | /* htonl() needed coz addr already in net order */ 894 | p = mdns_write_u32(p, htonl(rr->data.A.addr)); 895 | break; 896 | 897 | case RR_AAAA: 898 | for (i = 0; i < sizeof(struct in6_addr); i++) 899 | *p++ = rr->data.AAAA.addr->s6_addr[i]; 900 | break; 901 | 902 | case RR_PTR: 903 | label = rr->data.PTR.name ? 904 | rr->data.PTR.name : 905 | rr->data.PTR.entry->name; 906 | p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, label, comp); 907 | break; 908 | 909 | case RR_TXT: 910 | txt_rec = &rr->data.TXT; 911 | for (; txt_rec; txt_rec = txt_rec->next) { 912 | int len = txt_rec->txt[0] + 1; 913 | strncpy((char *) p, (char *) txt_rec->txt, len); 914 | p += len; 915 | } 916 | break; 917 | 918 | case RR_SRV: 919 | p = mdns_write_u16(p, rr->data.SRV.priority); 920 | 921 | p = mdns_write_u16(p, rr->data.SRV.weight); 922 | 923 | p = mdns_write_u16(p, rr->data.SRV.port); 924 | 925 | p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, 926 | rr->data.SRV.target, comp); 927 | break; 928 | 929 | case RR_NSEC: 930 | p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, 931 | rr->name, comp); 932 | 933 | *p++ = 0; // bitmap window/block number 934 | 935 | *p++ = sizeof(rr->data.NSEC.bitmap); // bitmap length 936 | 937 | for (i = 0; i < sizeof(rr->data.NSEC.bitmap); i++) 938 | *p++ = rr->data.NSEC.bitmap[i]; 939 | 940 | break; 941 | 942 | default: 943 | // DEBUG_PRINTF("unhandled rr type 0x%02x\n", rr->type); 944 | ; 945 | } 946 | 947 | // calculate data length based on p 948 | l = p - p_data; 949 | 950 | // fill in the length 951 | mdns_write_u16(p - l - sizeof(uint16_t), l); 952 | 953 | return p - pkt_buf - off; 954 | } 955 | 956 | // encodes a MDNS packet from the given mdns_pkt struct into a buffer 957 | // returns the size of the entire MDNS packet 958 | size_t mdns_encode_pkt(struct mdns_pkt *answer, uint8_t *pkt_buf, size_t pkt_len) { 959 | struct name_comp *comp; 960 | uint8_t *p = pkt_buf; 961 | //uint8_t *e = pkt_buf + pkt_len; 962 | size_t off; 963 | int i; 964 | 965 | assert(answer != NULL); 966 | assert(pkt_len >= 12); 967 | 968 | if (p == NULL) 969 | return -1; 970 | 971 | // this is an Answer - number of qns should be zero 972 | assert(answer->num_qn == 0); 973 | 974 | p = mdns_write_u16(p, answer->id); 975 | p = mdns_write_u16(p, answer->flags); 976 | p = mdns_write_u16(p, answer->num_qn); 977 | p = mdns_write_u16(p, answer->num_ans_rr); 978 | p = mdns_write_u16(p, answer->num_auth_rr); 979 | p = mdns_write_u16(p, answer->num_add_rr); 980 | 981 | off = p - pkt_buf; 982 | 983 | // allocate list for name compression 984 | comp = malloc(sizeof(struct name_comp)); 985 | if (comp == NULL) 986 | return -1; 987 | memset(comp, 0, sizeof(struct name_comp)); 988 | 989 | // dummy entry 990 | comp->label = (uint8_t *) ""; 991 | comp->pos = 0; 992 | 993 | // skip encoding of qn 994 | 995 | struct rr_list *rr_set[] = { 996 | answer->rr_ans, 997 | answer->rr_auth, 998 | answer->rr_add 999 | }; 1000 | 1001 | // encode answer, authority and additional RRs 1002 | for (i = 0; i < sizeof(rr_set) / sizeof(rr_set[0]); i++) { 1003 | struct rr_list *rr = rr_set[i]; 1004 | for (; rr; rr = rr->next) { 1005 | size_t l = mdns_encode_rr(pkt_buf, pkt_len, off, rr->e, comp); 1006 | off += l; 1007 | 1008 | if (off >= pkt_len) { 1009 | // DEBUG_PRINTF("packet buffer too small\n"); 1010 | return -1; 1011 | } 1012 | } 1013 | 1014 | } 1015 | 1016 | // free name compression list 1017 | while (comp) { 1018 | struct name_comp *c = comp->next; 1019 | free(comp); 1020 | comp = c; 1021 | } 1022 | 1023 | return off; 1024 | } 1025 | 1026 | //******************************************************// 1027 | // mdnsd.c // 1028 | //******************************************************// 1029 | 1030 | #ifdef _WIN32 1031 | #include 1032 | #include 1033 | #define LOG_ERR 3 1034 | #else 1035 | #include 1036 | #include 1037 | #include 1038 | #include 1039 | #include 1040 | #include 1041 | #include 1042 | #endif 1043 | 1044 | #include 1045 | #include 1046 | #include 1047 | #include 1048 | #include 1049 | #include 1050 | #include 1051 | #include 1052 | #include 1053 | #include 1054 | #include 1055 | 1056 | /* 1057 | * Define a proper IP socket level if not already done. 1058 | * Required to compile on OS X 1059 | */ 1060 | #ifndef SOL_IP 1061 | #define SOL_IP IPPROTO_IP 1062 | #endif 1063 | 1064 | #define MDNS_ADDR "224.0.0.251" 1065 | #define MDNS_PORT 5353 1066 | 1067 | #define PACKET_SIZE 65536 1068 | 1069 | #define SERVICES_DNS_SD_NLABEL \ 1070 | ((uint8_t *) "\x09_services\x07_dns-sd\x04_udp\x05local") 1071 | 1072 | struct mdnsd { 1073 | pthread_mutex_t data_lock; 1074 | int sockfd; 1075 | int notify_pipe[2]; 1076 | int stop_flag; 1077 | 1078 | struct rr_group *group; 1079 | struct rr_list *announce; 1080 | struct rr_list *services; 1081 | uint8_t *hostname; 1082 | }; 1083 | 1084 | struct mdns_service { 1085 | struct rr_list *entries; 1086 | }; 1087 | 1088 | ///////////////////////////////// 1089 | 1090 | 1091 | 1092 | static int create_recv_sock() { 1093 | int sd = socket(AF_INET, SOCK_DGRAM, 0); 1094 | if (sd < 0) { 1095 | // log_message(LOG_ERR, "recv socket(): %m"); 1096 | return sd; 1097 | } 1098 | 1099 | int r = -1; 1100 | 1101 | int on = 1; 1102 | if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on))) < 0) { 1103 | // log_message(LOG_ERR, "recv setsockopt(SO_REUSEADDR): %m"); 1104 | return r; 1105 | } 1106 | 1107 | /* bind to an address */ 1108 | struct sockaddr_in serveraddr; 1109 | memset(&serveraddr, 0, sizeof(serveraddr)); 1110 | serveraddr.sin_family = AF_INET; 1111 | serveraddr.sin_port = htons(MDNS_PORT); 1112 | serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /* receive multicast */ 1113 | if ((r = bind(sd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0) { 1114 | // log_message(LOG_ERR, "recv bind(): %m"); 1115 | } 1116 | 1117 | // add membership to receiving socket 1118 | struct ip_mreq mreq; 1119 | memset(&mreq, 0, sizeof(struct ip_mreq)); 1120 | mreq.imr_interface.s_addr = htonl(INADDR_ANY); 1121 | mreq.imr_multiaddr.s_addr = inet_addr(MDNS_ADDR); 1122 | if ((r = setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, sizeof(mreq))) < 0) { 1123 | // log_message(LOG_ERR, "recv setsockopt(IP_ADD_MEMBERSHIP): %m"); 1124 | return r; 1125 | } 1126 | 1127 | // enable loopback in case someone else needs the data 1128 | if ((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &on, sizeof(on))) < 0) { 1129 | // log_message(LOG_ERR, "recv setsockopt(IP_MULTICAST_LOOP): %m"); 1130 | return r; 1131 | } 1132 | 1133 | 1134 | #ifdef IP_PKTINFO 1135 | if ((r = setsockopt(sd, SOL_IP, IP_PKTINFO, (char *) &on, sizeof(on))) < 0) { 1136 | // log_message(LOG_ERR, "recv setsockopt(IP_PKTINFO): %m"); 1137 | return r; 1138 | } 1139 | #endif 1140 | 1141 | return sd; 1142 | } 1143 | 1144 | static ssize_t send_packet(int fd, const void *data, size_t len) { 1145 | static struct sockaddr_in toaddr; 1146 | if (toaddr.sin_family != AF_INET) { 1147 | memset(&toaddr, 0, sizeof(struct sockaddr_in)); 1148 | toaddr.sin_family = AF_INET; 1149 | toaddr.sin_port = htons(MDNS_PORT); 1150 | toaddr.sin_addr.s_addr = inet_addr(MDNS_ADDR); 1151 | } 1152 | 1153 | return sendto(fd, data, len, 0, (struct sockaddr *) &toaddr, sizeof(struct sockaddr_in)); 1154 | } 1155 | 1156 | 1157 | // populate the specified list which matches the RR name and type 1158 | // type can be RR_ANY, which populates all entries EXCEPT RR_NSEC 1159 | static int populate_answers(struct mdnsd *svr, struct rr_list **rr_head, uint8_t *name, enum rr_type type) { 1160 | int num_ans = 0; 1161 | 1162 | // check if we have the records 1163 | pthread_mutex_lock(&svr->data_lock); 1164 | struct rr_group *ans_grp = rr_group_find(svr->group, name); 1165 | if (ans_grp == NULL) { 1166 | pthread_mutex_unlock(&svr->data_lock); 1167 | return num_ans; 1168 | } 1169 | 1170 | // decide which records should go into answers 1171 | struct rr_list *n = ans_grp->rr; 1172 | for (; n; n = n->next) { 1173 | // exclude NSEC for RR_ANY 1174 | if (type == RR_ANY && n->e->type == RR_NSEC) 1175 | continue; 1176 | 1177 | if ((type == n->e->type || type == RR_ANY) && cmp_nlabel(name, n->e->name) == 0) { 1178 | num_ans += rr_list_append(rr_head, n->e); 1179 | } 1180 | } 1181 | 1182 | pthread_mutex_unlock(&svr->data_lock); 1183 | 1184 | return num_ans; 1185 | } 1186 | 1187 | // given a list of RRs, look up related records and add them 1188 | static void add_related_rr(struct mdnsd *svr, struct rr_list *list, struct mdns_pkt *reply) { 1189 | for (; list; list = list->next) { 1190 | struct rr_entry *ans = list->e; 1191 | 1192 | switch (ans->type) { 1193 | case RR_PTR: 1194 | // target host A, AAAA records 1195 | reply->num_add_rr += populate_answers(svr, &reply->rr_add, 1196 | MDNS_RR_GET_PTR_NAME(ans), RR_ANY); 1197 | break; 1198 | 1199 | case RR_SRV: 1200 | // target host A, AAAA records 1201 | reply->num_add_rr += populate_answers(svr, &reply->rr_add, 1202 | ans->data.SRV.target, RR_ANY); 1203 | 1204 | // perhaps TXT records of the same name? 1205 | // if we use RR_ANY, we risk pulling in the same RR_SRV 1206 | reply->num_add_rr += populate_answers(svr, &reply->rr_add, 1207 | ans->name, RR_TXT); 1208 | break; 1209 | 1210 | case RR_A: 1211 | case RR_AAAA: 1212 | reply->num_add_rr += populate_answers(svr, &reply->rr_add, 1213 | ans->name, RR_NSEC); 1214 | break; 1215 | 1216 | default: 1217 | // nothing to add 1218 | break; 1219 | } 1220 | } 1221 | } 1222 | 1223 | // creates an announce packet given the type name PTR 1224 | static void announce_srv(struct mdnsd *svr, struct mdns_pkt *reply, uint8_t *name) { 1225 | mdns_init_reply(reply, 0); 1226 | 1227 | reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, name, RR_PTR); 1228 | 1229 | // remember to add the services dns-sd PTR too 1230 | reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, 1231 | SERVICES_DNS_SD_NLABEL, RR_PTR); 1232 | 1233 | // see if we can match additional records for answers 1234 | add_related_rr(svr, reply->rr_ans, reply); 1235 | 1236 | // additional records for additional records 1237 | add_related_rr(svr, reply->rr_add, reply); 1238 | } 1239 | 1240 | // processes the incoming MDNS packet 1241 | // returns >0 if processed, 0 otherwise 1242 | static int process_mdns_pkt(struct mdnsd *svr, struct mdns_pkt *pkt, struct mdns_pkt *reply) { 1243 | int i; 1244 | 1245 | assert(pkt != NULL); 1246 | 1247 | // is it standard query? 1248 | if ((pkt->flags & MDNS_FLAG_RESP) == 0 && 1249 | MDNS_FLAG_GET_OPCODE(pkt->flags) == 0) { 1250 | mdns_init_reply(reply, pkt->id); 1251 | 1252 | // DEBUG_PRINTF("flags = %04x, qn = %d, ans = %d, add = %d\n", 1253 | // pkt->flags, 1254 | // pkt->num_qn, 1255 | // pkt->num_ans_rr, 1256 | // pkt->num_add_rr); 1257 | 1258 | // loop through questions 1259 | struct rr_list *qnl = pkt->rr_qn; 1260 | for (i = 0; i < pkt->num_qn; i++, qnl = qnl->next) { 1261 | struct rr_entry *qn = qnl->e; 1262 | int num_ans_added = 0; 1263 | 1264 | char *namestr = nlabel_to_str(qn->name); 1265 | // DEBUG_PRINTF("qn #%d: type %s (%02x) %s - ", i, rr_get_type_name(qn->type), qn->type, namestr); 1266 | free(namestr); 1267 | 1268 | // check if it's a unicast query - we ignore those 1269 | if (qn->unicast_query) { 1270 | // DEBUG_PRINTF("skipping unicast query\n"); 1271 | continue; 1272 | } 1273 | 1274 | num_ans_added = populate_answers(svr, &reply->rr_ans, qn->name, qn->type); 1275 | reply->num_ans_rr += num_ans_added; 1276 | 1277 | // DEBUG_PRINTF("added %d answers\n", num_ans_added); 1278 | } 1279 | 1280 | // remove our replies if they were already in their answers 1281 | struct rr_list *ans = NULL, *prev_ans = NULL; 1282 | for (ans = reply->rr_ans; ans; ) { 1283 | struct rr_list *next_ans = ans->next; 1284 | struct rr_entry *known_ans = rr_entry_match(pkt->rr_ans, ans->e); 1285 | 1286 | // discard answers that have at least half of the actual TTL 1287 | if (known_ans != NULL && known_ans->ttl >= ans->e->ttl / 2) { 1288 | char *namestr = nlabel_to_str(ans->e->name); 1289 | // DEBUG_PRINTF("removing answer for %s\n", namestr); 1290 | free(namestr); 1291 | 1292 | // check if list item is head 1293 | if (prev_ans == NULL) 1294 | reply->rr_ans = ans->next; 1295 | else 1296 | prev_ans->next = ans->next; 1297 | free(ans); 1298 | 1299 | ans = prev_ans; 1300 | 1301 | // adjust answer count 1302 | reply->num_ans_rr--; 1303 | } 1304 | 1305 | prev_ans = ans; 1306 | ans = next_ans; 1307 | } 1308 | 1309 | 1310 | // see if we can match additional records for answers 1311 | add_related_rr(svr, reply->rr_ans, reply); 1312 | 1313 | // additional records for additional records 1314 | add_related_rr(svr, reply->rr_add, reply); 1315 | 1316 | // DEBUG_PRINTF("\n"); 1317 | 1318 | return reply->num_ans_rr; 1319 | } 1320 | 1321 | return 0; 1322 | } 1323 | 1324 | int create_pipe(int handles[2]) { 1325 | #ifdef _WIN32 1326 | SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); 1327 | if (sock == INVALID_SOCKET) { 1328 | return -1; 1329 | } 1330 | struct sockaddr_in serv_addr; 1331 | memset(&serv_addr, 0, sizeof(serv_addr)); 1332 | serv_addr.sin_family = AF_INET; 1333 | serv_addr.sin_port = htons(0); 1334 | serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 1335 | if (bind(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) { 1336 | closesocket(sock); 1337 | return -1; 1338 | } 1339 | if (listen(sock, 1) == SOCKET_ERROR) { 1340 | closesocket(sock); 1341 | return -1; 1342 | } 1343 | int len = sizeof(serv_addr); 1344 | if (getsockname(sock, (SOCKADDR*)&serv_addr, &len) == SOCKET_ERROR) { 1345 | closesocket(sock); 1346 | return -1; 1347 | } 1348 | if ((handles[1] = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { 1349 | closesocket(sock); 1350 | return -1; 1351 | } 1352 | if (connect(handles[1], (struct sockaddr*)&serv_addr, len) == SOCKET_ERROR) { 1353 | closesocket(sock); 1354 | return -1; 1355 | } 1356 | if ((handles[0] = accept(sock, (struct sockaddr*)&serv_addr, &len)) == INVALID_SOCKET) { 1357 | closesocket((SOCKET)handles[1]); 1358 | handles[1] = INVALID_SOCKET; 1359 | closesocket(sock); 1360 | return -1; 1361 | } 1362 | closesocket(sock); 1363 | return 0; 1364 | #else 1365 | return pipe(handles); 1366 | #endif 1367 | } 1368 | 1369 | int read_pipe(int s, char* buf, int len) { 1370 | #ifdef _WIN32 1371 | int ret = recv(s, buf, len, 0); 1372 | if (ret < 0 && WSAGetLastError() == WSAECONNRESET) { 1373 | ret = 0; 1374 | } 1375 | return ret; 1376 | #else 1377 | return read(s, buf, len); 1378 | #endif 1379 | } 1380 | 1381 | int write_pipe(int s, char* buf, int len) { 1382 | #ifdef _WIN32 1383 | return send(s, buf, len, 0); 1384 | #else 1385 | return write(s, buf, len); 1386 | #endif 1387 | } 1388 | 1389 | int close_pipe(int s) { 1390 | #ifdef _WIN32 1391 | return closesocket(s); 1392 | #else 1393 | return close(s); 1394 | #endif 1395 | } 1396 | 1397 | // main loop to receive, process and send out MDNS replies 1398 | // also handles MDNS service announces 1399 | static void main_loop(struct mdnsd *svr) { 1400 | fd_set sockfd_set; 1401 | int max_fd = svr->sockfd; 1402 | char notify_buf[2]; // buffer for reading of notify_pipe 1403 | 1404 | void *pkt_buffer = malloc(PACKET_SIZE); 1405 | 1406 | if (svr->notify_pipe[0] > max_fd) 1407 | max_fd = svr->notify_pipe[0]; 1408 | 1409 | struct mdns_pkt *mdns_reply = malloc(sizeof(struct mdns_pkt)); 1410 | memset(mdns_reply, 0, sizeof(struct mdns_pkt)); 1411 | 1412 | while (! svr->stop_flag) { 1413 | FD_ZERO(&sockfd_set); 1414 | FD_SET(svr->sockfd, &sockfd_set); 1415 | FD_SET(svr->notify_pipe[0], &sockfd_set); 1416 | select(max_fd + 1, &sockfd_set, NULL, NULL, NULL); 1417 | 1418 | if (FD_ISSET(svr->notify_pipe[0], &sockfd_set)) { 1419 | // flush the notify_pipe 1420 | read_pipe(svr->notify_pipe[0], (char*)¬ify_buf, 1); 1421 | } else if (FD_ISSET(svr->sockfd, &sockfd_set)) { 1422 | struct sockaddr_in fromaddr; 1423 | socklen_t sockaddr_size = sizeof(struct sockaddr_in); 1424 | 1425 | ssize_t recvsize = recvfrom(svr->sockfd, pkt_buffer, PACKET_SIZE, 0, 1426 | (struct sockaddr *) &fromaddr, &sockaddr_size); 1427 | if (recvsize < 0) { 1428 | // log_message(LOG_ERR, "recv(): %m"); 1429 | } 1430 | 1431 | // DEBUG_PRINTF("data from=%s size=%ld\n", inet_ntoa(fromaddr.sin_addr), (long) recvsize); 1432 | struct mdns_pkt *mdns = mdns_parse_pkt(pkt_buffer, recvsize); 1433 | if (mdns != NULL) { 1434 | if (process_mdns_pkt(svr, mdns, mdns_reply)) { 1435 | size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE); 1436 | send_packet(svr->sockfd, pkt_buffer, replylen); 1437 | } else if (mdns->num_qn == 0) { 1438 | // DEBUG_PRINTF("(no questions in packet)\n\n"); 1439 | } 1440 | 1441 | mdns_pkt_destroy(mdns); 1442 | } 1443 | } 1444 | 1445 | // send out announces 1446 | while (1) { 1447 | struct rr_entry *ann_e = NULL; 1448 | 1449 | // extract from head of list 1450 | pthread_mutex_lock(&svr->data_lock); 1451 | if (svr->announce) 1452 | ann_e = rr_list_remove(&svr->announce, svr->announce->e); 1453 | pthread_mutex_unlock(&svr->data_lock); 1454 | 1455 | if (! ann_e) 1456 | break; 1457 | 1458 | char *namestr = nlabel_to_str(ann_e->name); 1459 | // DEBUG_PRINTF("sending announce for %s\n", namestr); 1460 | free(namestr); 1461 | 1462 | announce_srv(svr, mdns_reply, ann_e->name); 1463 | 1464 | if (mdns_reply->num_ans_rr > 0) { 1465 | size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE); 1466 | send_packet(svr->sockfd, pkt_buffer, replylen); 1467 | } 1468 | } 1469 | } 1470 | 1471 | // main thread terminating. send out "goodbye packets" for services 1472 | mdns_init_reply(mdns_reply, 0); 1473 | 1474 | pthread_mutex_lock(&svr->data_lock); 1475 | struct rr_list *svc_le = svr->services; 1476 | for (; svc_le; svc_le = svc_le->next) { 1477 | // set TTL to zero 1478 | svc_le->e->ttl = 0; 1479 | mdns_reply->num_ans_rr += rr_list_append(&mdns_reply->rr_ans, svc_le->e); 1480 | } 1481 | pthread_mutex_unlock(&svr->data_lock); 1482 | 1483 | // send out packet 1484 | if (mdns_reply->num_ans_rr > 0) { 1485 | size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE); 1486 | send_packet(svr->sockfd, pkt_buffer, replylen); 1487 | } 1488 | 1489 | // destroy packet 1490 | mdns_init_reply(mdns_reply, 0); 1491 | free(mdns_reply); 1492 | 1493 | free(pkt_buffer); 1494 | 1495 | close_pipe(svr->sockfd); 1496 | 1497 | svr->stop_flag = 2; 1498 | } 1499 | 1500 | ///////////////////////////////////////////////////// 1501 | 1502 | 1503 | void mdnsd_set_hostname(struct mdnsd *svr, const char *hostname, uint32_t ip) { 1504 | struct rr_entry *a_e = NULL, 1505 | *nsec_e = NULL; 1506 | 1507 | // currently can't be called twice 1508 | // dont ask me what happens if the IP changes 1509 | assert(svr->hostname == NULL); 1510 | 1511 | a_e = rr_create_a(create_nlabel(hostname), ip); // 120 seconds automatically 1512 | 1513 | nsec_e = rr_create(create_nlabel(hostname), RR_NSEC); 1514 | nsec_e->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // set to 120 seconds (default is 4500) 1515 | rr_set_nsec(nsec_e, RR_A); 1516 | 1517 | pthread_mutex_lock(&svr->data_lock); 1518 | svr->hostname = create_nlabel(hostname); 1519 | rr_group_add(&svr->group, a_e); 1520 | rr_group_add(&svr->group, nsec_e); 1521 | pthread_mutex_unlock(&svr->data_lock); 1522 | } 1523 | 1524 | void mdnsd_set_hostname_v6(struct mdnsd *svr, const char *hostname, struct in6_addr *addr) 1525 | { 1526 | struct rr_entry *aaaa_e = NULL, 1527 | *nsec_e = NULL; 1528 | 1529 | // currently can't be called twice 1530 | // dont ask me what happens if the IP changes 1531 | assert(svr->hostname == NULL); 1532 | 1533 | aaaa_e = rr_create_aaaa(create_nlabel(hostname), addr); // 120 seconds automatically 1534 | 1535 | nsec_e = rr_create(create_nlabel(hostname), RR_NSEC); 1536 | nsec_e->ttl = DEFAULT_TTL_FOR_RECORD_WITH_HOSTNAME; // set to 120 seconds (default is 4500) 1537 | rr_set_nsec(nsec_e, RR_AAAA); 1538 | 1539 | pthread_mutex_lock(&svr->data_lock); 1540 | svr->hostname = create_nlabel(hostname); 1541 | rr_group_add(&svr->group, aaaa_e); 1542 | rr_group_add(&svr->group, nsec_e); 1543 | pthread_mutex_unlock(&svr->data_lock); 1544 | } 1545 | 1546 | void mdnsd_add_rr(struct mdnsd *svr, struct rr_entry *rr) { 1547 | pthread_mutex_lock(&svr->data_lock); 1548 | rr_group_add(&svr->group, rr); 1549 | pthread_mutex_unlock(&svr->data_lock); 1550 | } 1551 | 1552 | struct mdns_service *mdnsd_register_svc(struct mdnsd *svr, const char *instance_name, 1553 | const char *type, uint16_t port, const char *hostname, const char *txt[]) { 1554 | struct rr_entry *txt_e = NULL, 1555 | *srv_e = NULL, 1556 | *ptr_e = NULL, 1557 | *bptr_e = NULL; 1558 | uint8_t *target; 1559 | uint8_t *inst_nlabel, *type_nlabel, *nlabel; 1560 | struct mdns_service *service = malloc(sizeof(struct mdns_service)); 1561 | memset(service, 0, sizeof(struct mdns_service)); 1562 | 1563 | // combine service name 1564 | type_nlabel = create_nlabel(type); 1565 | inst_nlabel = create_label(instance_name); 1566 | nlabel = join_nlabel(inst_nlabel, type_nlabel); 1567 | 1568 | // create TXT record 1569 | if (txt && *txt) { 1570 | txt_e = rr_create(dup_nlabel(nlabel), RR_TXT); // automatically 4500 seconds 1571 | rr_list_append(&service->entries, txt_e); 1572 | 1573 | // add TXTs 1574 | for (; *txt; txt++) 1575 | rr_add_txt(txt_e, *txt); 1576 | } 1577 | 1578 | // create SRV record 1579 | assert(hostname || svr->hostname); // either one as target 1580 | target = hostname ? 1581 | create_nlabel(hostname) : 1582 | dup_nlabel(svr->hostname); 1583 | 1584 | srv_e = rr_create_srv(dup_nlabel(nlabel), port, target); // automatically 4500 seconds 1585 | rr_list_append(&service->entries, srv_e); 1586 | 1587 | // create PTR record for type 1588 | ptr_e = rr_create_ptr(type_nlabel, srv_e); // automatically 4500 seconds 1589 | 1590 | // create services PTR record for type 1591 | // this enables the type to show up as a "service" 1592 | bptr_e = rr_create_ptr(dup_nlabel(SERVICES_DNS_SD_NLABEL), ptr_e); // automatically 4500 seconds 1593 | 1594 | // modify lists here 1595 | pthread_mutex_lock(&svr->data_lock); 1596 | 1597 | if (txt_e) 1598 | rr_group_add(&svr->group, txt_e); 1599 | rr_group_add(&svr->group, srv_e); 1600 | rr_group_add(&svr->group, ptr_e); 1601 | rr_group_add(&svr->group, bptr_e); 1602 | 1603 | // append PTR entry to announce list 1604 | rr_list_append(&svr->announce, ptr_e); 1605 | rr_list_append(&svr->services, ptr_e); 1606 | 1607 | pthread_mutex_unlock(&svr->data_lock); 1608 | 1609 | // don't free type_nlabel - it's with the PTR record 1610 | free(nlabel); 1611 | free(inst_nlabel); 1612 | 1613 | // notify server 1614 | write_pipe(svr->notify_pipe[1], ".", 1); 1615 | 1616 | return service; 1617 | } 1618 | 1619 | void mdns_service_destroy(struct mdns_service *srv) { 1620 | assert(srv != NULL); 1621 | rr_list_destroy(srv->entries, 0); 1622 | free(srv); 1623 | } 1624 | 1625 | struct mdnsd *mdnsd_start() { 1626 | pthread_t tid; 1627 | pthread_attr_t attr; 1628 | 1629 | struct mdnsd *server = malloc(sizeof(struct mdnsd)); 1630 | memset(server, 0, sizeof(struct mdnsd)); 1631 | 1632 | if (create_pipe(server->notify_pipe) != 0) { 1633 | // log_message(LOG_ERR, "pipe(): %m\n"); 1634 | free(server); 1635 | return NULL; 1636 | } 1637 | 1638 | server->sockfd = create_recv_sock(); 1639 | if (server->sockfd < 0) { 1640 | // log_message(LOG_ERR, "unable to create recv socket"); 1641 | free(server); 1642 | return NULL; 1643 | } 1644 | 1645 | pthread_mutex_init(&server->data_lock, NULL); 1646 | 1647 | // init thread 1648 | pthread_attr_init(&attr); 1649 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 1650 | 1651 | if (pthread_create(&tid, &attr, (void *(*)(void *)) main_loop, (void *) server) != 0) { 1652 | pthread_mutex_destroy(&server->data_lock); 1653 | free(server); 1654 | return NULL; 1655 | } 1656 | 1657 | return server; 1658 | } 1659 | 1660 | void mdnsd_stop(struct mdnsd *s) { 1661 | assert(s != NULL); 1662 | 1663 | struct timeval tv = { 1664 | .tv_sec = 0, 1665 | .tv_usec = 500 * 1000, 1666 | }; 1667 | 1668 | s->stop_flag = 1; 1669 | write_pipe(s->notify_pipe[1], ".", 1); 1670 | 1671 | while (s->stop_flag != 2) 1672 | select(0, NULL, NULL, NULL, &tv); 1673 | 1674 | close_pipe(s->notify_pipe[0]); 1675 | close_pipe(s->notify_pipe[1]); 1676 | 1677 | pthread_mutex_destroy(&s->data_lock); 1678 | rr_group_destroy(s->group); 1679 | rr_list_destroy(s->announce, 0); 1680 | rr_list_destroy(s->services, 0); 1681 | 1682 | if (s->hostname) 1683 | free(s->hostname); 1684 | 1685 | free(s); 1686 | } 1687 | 1688 | --------------------------------------------------------------------------------