Simple mDNS client/server library in Golang

mdns

Simple mDNS client/server library in Golang. mDNS or Multicast DNS can be used to discover services on the local network without the use of an authoritative DNS server. This enables peer-to-peer discovery. It is important to note that many networks restrict the use of multicasting, which prevents mDNS from functioning. Notably, multicast cannot be used in any sort of cloud, or shared infrastructure environment. However it works well in most office, home, or private infrastructure environments.

Using the library is very simple, here is an example of publishing a service entry:

// Setup our service export
host, _ := os.Hostname()
info := []string{"My awesome service"}
service, _ := mdns.NewMDNSService(host, "_foobar._tcp", "", "", 8000, nil, info)

// Create the mDNS server, defer shutdown
server, _ := mdns.NewServer(&mdns.Config{Zone: service})
defer server.Shutdown()

Doing a lookup for service providers is also very simple:

// Make a channel for results and start listening
entriesCh := make(chan *mdns.ServiceEntry, 4)
go func() {
    for entry := range entriesCh {
        fmt.Printf("Got new entry: %v\n", entry)
    }
}()

// Start the lookup
mdns.Lookup("_foobar._tcp", entriesCh)
close(entriesCh)
Owner
HashiCorp
Consistent workflows to provision, secure, connect, and run any infrastructure for any application.
HashiCorp
Comments
  • Decreased reliance on explicit IP addressing and updates to DNS record presentation

    Decreased reliance on explicit IP addressing and updates to DNS record presentation

    I made a number of changes to this library based on a need for decreasing the reliance on explicit IP addressing. The full diff of changes can be viewed at https://github.com/richtr/mdns/compare/armon:master...master.

    The summary of the changes is as follows:

    1. Don't require an upfront IP address to be passed in when creating a new mDNS service. Instead, determine the local machine's FQDN and then resolve this to both its IPv4 and IPv6 addresses implicitly.

    2. When an mDNS query is received then return both a service's A record and its AAAA record (in line with other mDNS library behavior) rather than filtering out one or the other.

    3. Ensure the mDNS SRV record response provides a resolvable FQDN address as per the spec.

      From this:

      myservice._ws._tcp.local. 10  IN  SRV  10 1 8080 myservice._ws._tcp.local.
      

      to this:

      myservice._ws._tcp.local. 10  IN  SRV  10 1 8080 MyMacBook.local.
      
    4. Ensure mDNS A and AAAA record responses relate to the resolvable SRV name as per the spec:

      From this:

      myservice._ws._tcp.local. 10  IN  A     127.0.0.1
      myservice._ws._tcp.local. 10  IN  AAAA  ::1
      

      to this:

      MyMacBook.local. 10  IN  A     10.112.0.152
      MyMacBook.local. 10  IN  AAAA  fe80::7aca:39ff:feb4:42c1
      
    5. Update the tests and README example according to the changes above.

    Are these changes that we could incorporate in to this library somehow? If you would like this in the form of a pull request just let me know.

  • DNS-SD Client Support

    DNS-SD Client Support

    Re-worked mdns client to cache all over the wire results, replay the cache for a new query, and provide additional RFC6763 data to lookups (eg parsing TXT records). Additionally, mdns lookups can be made directly for a record (eg a SRV record) instead of only being able to resolve a service.

  • Tests failing on OS X

    Tests failing on OS X

    Hi,

    I'm unable to both lookup or register new mDNS services on my local network. Running go test *.go in the root folder of this package consistently outputs the following:

    2014/05/21 15:47:48 [ERR] mdns: Failed to start IPv6 listener: listen udp6 ff02::fb: setsockopt: can't assign requested address
    2014/05/21 15:47:48 [ERR] mdns: Failed to start IPv6 listener: listen udp6 ff02::fb: setsockopt: can't assign requested address
    --- FAIL: TestServer_Lookup (0.05 seconds)
    server_test.go:63: record not found
    FAIL
    FAIL    command-line-arguments  0.067s
    

    I'm running OS X 10.9.2 and Go version 1.2 (darwin/amd64).

    Any ideas on why the tests would be failing on my setup?

  • Allow disabling either IPv4 or IPv6.

    Allow disabling either IPv4 or IPv6.

    This is particularly useful in situations where the underlying network doesn’t support IPv6. In particular its possible when running in docker that the OS allows setting up the IPv6 socket but when you go to send on it, then it will fail.

    Also the test was refactored to fix some linter warnings about calling t.Fatalf from within a non-test go routine. Now that go routine will emit an error from a chan and the main test routine receives from it to determine whether the test should pass or fail.

  • Look up does not end forever

    Look up does not end forever

    Hi @armon This issue is probably my misunderstood, but I ask you for check.

    Situation

    I use mdns.Lookup:

    package main
    
    import (
        "github.com/armon/mdns"
        "fmt"
    )
    
    func main() {
        entriesCh := make(chan *mdns.ServiceEntry, 1)
    
        go func() {
            for entry := range entriesCh {
                fmt.Printf("Got new entry: %v\n", entry)
            }
        }()
    
        mdns.Lookup("_airplay._tcp", entriesCh)
        close(entriesCh)
    }
    

    Then run $ go run main.go, nothing is printed. But I was able to search using dns-sd command:

    $ dns-sd -L "Apple TV" _airplay._tcp
    Lookup Apple TV._airplay._tcp.local
    DATE: ---Tue 20 May 2014---
    21:54:29.174  ...STARTING...
    21:54:29.299  Apple TV._airplay._tcp.local. can be reached at AppleTV.local.:7000 (interface 4)
     deviceid=(skip...)
    

    Print debug

    I tried to fix client.go based on 8be7e3ac

    diff --git a/client.go b/client.go
    index d449e4d..cfebe63 100644
    --- a/client.go
    +++ b/client.go
    @@ -186,17 +186,20 @@ func (c *client) query(params *QueryParam) error {
                                    case *dns.PTR:
                                            // Create new entry for this
                                            inp = ensureName(inprogress, rr.Ptr)
    +                                       fmt.Printf("PTR: %v\n", rr)
    
                                    case *dns.SRV:
                                            // Get the port
                                            inp = ensureName(inprogress, rr.Target)
                                            inp.Port = int(rr.Port)
    +                                       fmt.Printf("SRV: %v\n", rr)
    
                                    case *dns.TXT:
                                            // Pull out the txt
                                            inp = ensureName(inprogress, rr.Hdr.Name)
                                            inp.Info = strings.Join(rr.Txt, "|")
                                            inp.hasTXT = true
    +                                       fmt.Printf("TXT: %v\n", rr)
    
                                    case *dns.A:
                                            // Pull out the IP
    @@ -209,7 +212,7 @@ func (c *client) query(params *QueryParam) error {
                                            inp.Addr = rr.AAAA
                                    }
                            }
    -
    +                       fmt.Printf("    inp = %v\n", inp)
                            // Check if this entry is complete
                            if inp.complete() && !inp.sent {
                                    inp.sent = true
    @@ -221,10 +224,12 @@ func (c *client) query(params *QueryParam) error {
                                    // Fire off a node specific query
                                    m := new(dns.Msg)
                                    m.SetQuestion(inp.Name, dns.TypeANY)
    +                               fmt.Printf("Question: %v\n", m)
                                    if err := c.sendQuery(m); err != nil {
                                            log.Printf("[ERR] mdns: Failed to query instance %s: %v", inp.Name, err)
                                    }
                            }
    +                       fmt.Println("--------------------")
                    case <-finish:
                            return nil
    

    Then run:

    $ go run main.go
    PTR: _airplay._tcp.local.       10      IN      PTR     Apple\194\160TV._airplay._tcp.local.
        inp = &{Apple\194\160TV._airplay._tcp.local. <nil> 0  false false}
    Question: ;; opcode: QUERY, status: NOERROR, id: 44739
    ;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
    
    ;; QUESTION SECTION:
    ;Apple\194\160TV._airplay._tcp.local.   IN       ANY
    
    --------------------
    TXT: Apple\194\160TV._airplay._tcp.local.       10      IN      TXT     "deviceid=58:55:CA:0D:DB:FA" "features=0x4A7FFFF7,0xE" "flags=0x44" "model=AppleTV2,1" "pk=4c58e46591dd61a873659cfc32441ad6e51ead18939b1cc683ec008f36bbba0b" "srcvers=200.54" "vv=2"
    SRV: Apple\194\160TV._airplay._tcp.local.       10      IN      SRV     0 0 7000 AppleTV.local.
        inp = &{AppleTV.local. <nil> 7000  false false}
    Question: ;; opcode: QUERY, status: NOERROR, id: 30016
    ;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
    
    ;; QUESTION SECTION:
    ;AppleTV.local. IN       ANY
    
    --------------------
        inp = &{AppleTV.local. 192.168.0.2 7000  false false}
    Question: ;; opcode: QUERY, status: NOERROR, id: 38769
    ;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
    
    ;; QUESTION SECTION:
    ;AppleTV.local. IN       ANY
    
    --------------------
        inp = &{AppleTV.local. 192.168.0.2 7000  false false}
    Question: ;; opcode: QUERY, status: NOERROR, id: 23884
    ;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
    
    ;; QUESTION SECTION:
    ;AppleTV.local. IN       ANY
    
    (repeat until timeout..)
    

    Expected to cause of incomplete:

    1. When get PTR record, create new entry (inp.name = Apple\194\160TV._airplay._tcp.local.)
    2. When get TXT record, inp.hasTxt = true
    3. When get SRV record, create new entry (inp.name = AppleTV.local)
    4. Hereafter, question about AppleTV.local only sent, and never inp.hasTxt becomes true

    Supplementary

    I was able to reproduce the problem on _daap._tcp

  • Expose rr.Txt as []string instead of joining it with “|”.

    Expose rr.Txt as []string instead of joining it with “|”.

    Joining the different fields into a single string is problematic when the fields contain the separator character “|” themselves.

    As an example, I’ve named my chromecast “Hendrix|test”, and this happens when I print a ServiceEntry:

    ServiceEntry: &{Name:Hendrix|test._googlecast._tcp.local. Host:Hendrix|test.local. AddrV4:10.0.0.184 AddrV6: Port:8009 Info:id=3fc948dbfebcf5d7ec77d7b043dce81e|ve=04|md=Chromecast Audio|ic=/setup/icon.png|fn=Hendrix|test|ca=4|st=0|bs=FA8FCA928304|rs= Addr:10.0.0.184 hasTXT:true sent:true}

    When using strings.Split, I would not correctly parse the fn= field.

    With the new InfoFields member, this problem can be avoided entirely.

  • Unable to build example

    Unable to build example

    I'm trying to build example file of your package and get this

    example.go

    package main
    
    import (
        "fmt"
        "github.com/armon/mdns"
        "os"
    )
    
    func main() {
        host, _ := os.Hostname()
        service := &mdns.MDNSService{
            Instance: host,
            Service:  "_foobar._tcp",
            Port:     8000,
            Info:     "My awesome service",
        }
        service.Init()
    
        // Create the mDNS server, defer shutdown
        server, _ := mdns.NewServer(&mdns.Config{Zone: service})
        defer server.Shutdown()
    
        // Make a channel for results and start listening
        entriesCh := make(chan *mdns.ServiceEntry, 4)
        go func() {
            for entry := range entriesCh {
                fmt.Printf("Got new entry: %v\n", entry)
            }
        }()
    
        // Start the lookup
        mdns.Lookup("_foobar._tcp", entriesCh)
        close(entriesCh)
    }
    
    
    [vodolaz095@steel gopnik]$ go version
    go version go1.3.3 linux/amd64
    [vodolaz095@steel gopnik]$ go build discovery.go
    # code.google.com/p/go.net/ipv6
    /home/vodolaz095/projects/go/src/code.google.com/p/go.net/ipv6/control_rfc3542_unix.go:75: undefined: sysSizeofPacketInfo
    /home/vodolaz095/projects/go/src/code.google.com/p/go.net/ipv6/control_rfc3542_unix.go:78: undefined: sysSizeofMTUInfo
    /home/vodolaz095/projects/go/src/code.google.com/p/go.net/ipv6/control_rfc3542_unix.go:85: undefined: sysSockoptReceiveTrafficClass
    /home/vodolaz095/projects/go/src/code.google.com/p/go.net/ipv6/control_rfc3542_unix.go:92: undefined: sysSockoptReceiveHopLimit
    /home/vodolaz095/projects/go/src/code.google.com/p/go.net/ipv6/control_rfc3542_unix.go:99: undefined: sysSockoptReceivePacketInfo
    /home/vodolaz095/projects/go/src/code.google.com/p/go.net/ipv6/control_rfc3542_unix.go:100: undefined: sysSizeofPacketInfo
    /home/vodolaz095/projects/go/src/code.google.com/p/go.net/ipv6/control_rfc3542_unix.go:101: undefined: sysSizeofPacketInfo
    /home/vodolaz095/projects/go/src/code.google.com/p/go.net/ipv6/control_rfc3542_unix.go:106: undefined: sysSockoptReceivePathMTU
    /home/vodolaz095/projects/go/src/code.google.com/p/go.net/ipv6/control_rfc3542_unix.go:107: undefined: sysSizeofMTUInfo
    /home/vodolaz095/projects/go/src/code.google.com/p/go.net/ipv6/control_rfc3542_unix.go:108: undefined: sysSizeofMTUInfo
    /home/vodolaz095/projects/go/src/code.google.com/p/go.net/ipv6/control_rfc3542_unix.go:108: too many errors
    
    

    I'm quite new to go, and probably i builded something not properly - i just installed golang packages from rpm for this

  • CVE-2019-19794 on last release

    CVE-2019-19794 on last release

    Hello,

    The last release has a dependancies with github.com/miekg/dns v1.0.14 who's concerned by the CVE-2019-19794 :

    • https://snyk.io/vuln/SNYK-GOLANG-GITHUBCOMMIEKGDNS-537825
    • https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-19794

    Could you plan a new release to fix this dependance ?

    (and do same will all other repository of hashicorps who's including this repos)

    Thanks in advance.

  • Update LICENSE

    Update LICENSE

    Hi there 👋

    This PR was generated as part of an internal review of public repositories that are not in compliance with HashiCorp's licensing standards.

    Frequently Asked Questions

    Why am I getting this PR? This pull request was created because one or more of the following criteria was found:
    • This repo did not previously have a LICENSE file
    • A LICENSE file was present, but had a non-conforming name (e.g., license.txt)
    • A LICENSE file was present, but was missing an appropriate copyright statement

    More info is available in the RFC

    How do you determine the copyright date? The copyright date given in this PR is supposed to be the year the repository or project was created (whichever is older). If you believe the copyright date given in this PR is not valid, please reach out to:

    #proj-software-copyright

    Please approve and merge this PR in a timely manner to keep this source code compliant with our OSS license agreement. If you have any questions or feedback, reach out to #proj-software-copyright.

    Thank you!

  • dep: upgrade miekg/dns.

    dep: upgrade miekg/dns.

    Consul uses an outdated version of this dependency. Latest versions have deliberately broken the public API by removing dns.ErrTruncated. See https://github.com/miekg/dns/issues/814.

    This PR is part of a batch of PRs to update this module in the dependencies of hashicorp/consul, so it can be updated cleanly there.

  • Fix possible IP comparison error

    Fix possible IP comparison error

    A net.IP may be represented by both by a 4 as well as a 16 byte long byte slice. Because of this, it is not safe to compare IP addresses using bytes.Equal as the same IP address using a different internal representation will produce mismatches.

    Note, this PR was not initiated by my use of this package and noticing a bug, rather by creating an extension proposal for go vet and that signalling the possible misuse in this library.

    Cheers :)

  • Multiple Answer RRs are not sent as separate entries

    Multiple Answer RRs are not sent as separate entries

    Hello,

    Thank you for the library!

    I am using library to discover specific services on local network by querying service type. There can be multiple services of the same type from same machine/device. There is an issue I have encountered, query responses that have multiple answers are not reported as separate entries.

    Checkout wireshark screenshot:

    Screenshot 2022-04-06 at 16 16 50

    Here 192.168.80.92 is the address of device running the client code. 192.168.80.182 is the server which responds to mdns queries (it is windows machine, uses bonjour service). As you can see from screenshot, the are 2 services of type _itxpt_http._tcp. The Answer section contains 2 records, + additional records for each of these 2 answers.

    The client code:

    package main
    
    import (
    	"context"
    	"fmt"
    	"os"
    	"os/signal"
    	"time"
    
    	"github.com/hashicorp/mdns"
    )
    
    func main() {
    	timeout := 2 * time.Second
    	ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
    	ctx, cancel := context.WithTimeout(ctx, timeout)
    	defer cancel()
    
    	entriesCh := make(chan *mdns.ServiceEntry, 4)
    	go mdns.Query(&mdns.QueryParam{
    		Service:     "_itxpt_http._tcp",
    		Entries:     entriesCh,
    		DisableIPv6: true,
    		Domain:      "local",
    		Timeout:     timeout,
    	})
    
    	for {
    		select {
    		case entry := <-entriesCh:
    			fmt.Println("Got new entry:", entry.Name)
    		case <-ctx.Done():
    			fmt.Println("done")
    			return
    		}
    	}
    }
    

    Client only registers 1 entry, with values of second answer:

    $ go run client.go
    Got new entry: vehicle-1000_inventory._itxpt_http._tcp.local.
    done
    

    Is there a way to get these values as two separate entries? Like:

    Got new entry: vehicle-1000_avms._itxpt_http._tcp.local.
    Got new entry: vehicle-1000_inventory._itxpt_http._tcp.local.
    

    It seems like the entry gets overwritten with the data from last answer in this loop: https://github.com/hashicorp/mdns/blob/v1.0.5/client.go#L272

  • Do you have a plan to release v1.0.3 at Github?

    Do you have a plan to release v1.0.3 at Github?

    Hi, friend

    Great job! for this mDNS implementation!

    I found this module has a v1.0.3 release at pkg.go.dev, but not at Github repository here.

    Do you have a plan to release v1.0.3 at this code base?

    Thanks a lot! 👍 Mao

  • No results (Windows)

    No results (Windows)

    Seems to work fine on a Pi. Here's the same code running under Windows:

    c:\repo\src>go run mdns.go 2020/03/31 16:21:23 [ERR] mdns: Failed to bind to udp6 port: listen udp6 [ff02::fb]:5353: setsockopt: not supported by windows 2020/03/31 16:21:24 [ERR] mdns: Failed to read packet: read udp4 0.0.0.0:52447: use of closed network connection

Related tags
Server and client implementation of the grpc go libraries to perform unary, client streaming, server streaming and full duplex RPCs from gRPC go introduction

Description This is an implementation of a gRPC client and server that provides route guidance from gRPC Basics: Go tutorial. It demonstrates how to u

Nov 24, 2021
A golang library about socks5, supports all socks5 commands. That Provides server and client and easy to use. Compatible with socks4 and socks4a.

socks5 This is a Golang implementation of the Socks5 protocol library. To see in this SOCKS Protocol Version 5. This library is also compatible with S

Nov 22, 2022
Gosof - A simple and easy golang socket server/client framework

Golang Socket Framework What A simple and easy golang socket server/client framework especially convenient for handling TCP fixed-length header and va

Feb 27, 2022
A simple FTP protocol with client and server implemented in TypeScript and Golang

websocket-ftp A simple FTP protocol with client and server implemented in TypeScript and Golang. Example (Client) const buffer: Uint8Array = (new Text

Apr 14, 2022
An OOB interaction gathering server and client library
An OOB interaction gathering server and client library

Interactsh An OOB interaction gathering server and client library Features • Usage • Interactsh Client • Interactsh Server • Interactsh Integration •

Jan 2, 2023
Simple chat client that uses github.com/tydar/stomper as a backing server.

STOMP Chat Chat client to operate through a STOMP pub/sub server. Designed to demonstrate my project stomper. Todo to finish: Allow runtime configurat

Dec 23, 2021
Simple TCP proxy to visualise NATS client/server traffic
Simple TCP proxy to visualise NATS client/server traffic

NATS uses a simple publish/subscribe style plain-text protocol to communicate between a NATS Server and its clients. Whilst this connection should remain opaque to the user, it can be quite handy to see the data being passed from time to time - this tool does just that (it also saves me loading Wireshark and filtering the NATS traffic).

Jan 15, 2022
Go-grpc-tutorial - Simple gRPC server/client using go

Simple gRPC server/client using go Run server go run usermgmt_server/usermgmt_

Feb 14, 2022
Broadcast-server - A simple Go server that broadcasts any data/stream

broadcast A simple Go server that broadcasts any data/stream usage data You can

Oct 21, 2022
Godaddy-domains-client-go - Godaddy domains api Client golang - Write automaticly from swagger codegen

Go API client for swagger Overview This API client was generated by the swagger-codegen project. By using the swagger-spec from a remote server, you c

Jan 9, 2022
A LWM2M Client and Server implementation (For Go/Golang)

Betwixt - A LWM2M Client and Server in Go Betwixt is a Lightweight M2M implementation written in Go OMA Lightweight M2M is a protocol from the Open Mo

Dec 23, 2022
Server-client project with golang.
Server-client project with golang.

server-client-project Server-client project with golang. Figure 1 - Screenshot showing that the client does not work without the server running. Figur

Mar 3, 2022
Client-Server tcp-based file transfer application in GoLang

Клиент-серверный файловый сервис на базе протокола TCP Клиент client.go шифрует свои файлы алгоритмом AES с режимом CBC и помещает их на сервер server

Apr 1, 2022
A http-relay server/client written in golang to forward requests to a service behind a nat router from web

http-relay This repo is WIP http-relay is a server/client application written in go(lang) to forward http(s) requests to an application behind a nat r

Dec 16, 2021
Golang pow implementation client <-> server over UDP and TCP protocols
Golang pow implementation client <-> server over UDP and TCP protocols

Client <-> server over UDP and TCP pow protocol Denial-of-Service-attacks are a typical situation when providing services over a network. A method for

Jan 13, 2022
Simple REST client library in Go

Simple REST client library in Go Context The goal was to make a minimal library that could be easily reused and expanded in other projects. It doesn't

Sep 21, 2022
server-to-server sync application, written in go/golang.

svcpy: server to server copy a basic server-to-server copy application. on a single binary, it can be a server or a client. example usage: on the serv

Nov 4, 2021
Pape-server - A small server written in golang to serve a random wallpaper.

pape-server I like to inject custom CSS themes into a lot of websites and electron apps, however browsers don't let websites access local disk through

Dec 31, 2021