Giter Club home page Giter Club logo

mdns's Introduction

Mdns

A simple mDNS client for device discovery on your local network.

Installation

1. git clone https://github.com/NationalAssociationOfRealtors/mdns.git
2. mix do deps.get, deps.compile
3. iex -S mix

Server Usage

The udp server doesn't start automatically, this gives you a chance to bring up your network interface, before starting the server, especially useful when using this with Nerves. To start the server, call Mdns.Server.start. To handle :a record requests you will want to set the ip address. This can change periodically, or you may have multiple interfaces. Set or update the ip address used for query responses by calling Mdns.Server.set_ip({192, 168, 1, 4}).

To add a service to the server call Mdns.Server.add_service(%Mdns.Server.Service{}) an example service might looks like this.

Mdns.Server.add_service(%Mdns.Server.Service{
    domain: "nerves.local",
    data: "_rosetta._tcp.local",
    ttl: 120,
    type: :ptr
})

You can also add :a records so that your service is available from a web browser or pingable. In order to use the ip address set by calling Mdns.Server.set_ip in responses, use the :ip atom for the value of your data attribute in the Mdns.Server.Service struct.

Mdns.Server.add_service(%Mdns.Server.Service{
    domain: "rosetta.local",
    data: :ip, #this is a special atom that uses the ip address set when calling Mdns.Server.set_ip
    ttl: 120,
    type: :a
})

And :txt records as well.

Mdns.Server.add_service(%Mdns.Server.Service{
    domain: "_nerves._tcp.local",
    data: ["id=123123", "port=8800"],
    ttl: 120,
    type: :txt
})

To see the server and client in action run mix test and view the code in test/mdns_test.exs

Once an :a record has been added(with the correct ip) you should be able to run ping rosetta.local

:~$ ping rosetta.local
PING rosetta.local (192.168.1.4) 56(84) bytes of data.
64 bytes from 192.168.1.4: icmp_seq=1 ttl=64 time=0.279 ms
64 bytes from 192.168.1.4: icmp_seq=2 ttl=64 time=0.261 ms
64 bytes from 192.168.1.4: icmp_seq=3 ttl=64 time=0.329 ms
64 bytes from 192.168.1.4: icmp_seq=4 ttl=64 time=0.270 ms
64 bytes from 192.168.1.4: icmp_seq=5 ttl=64 time=0.227 ms
64 bytes from 192.168.1.4: icmp_seq=6 ttl=64 time=0.215 ms
^C
--- rosetta.local ping statistics ---
6 packets transmitted, 6 received, 0% packet loss, time 4998ms
rtt min/avg/max/mdev = 0.215/0.263/0.329/0.040 ms

Client Usage

Start the client by calling Mdns.Client.start, again, this gives the system an opportunity to bring up the network interface. To discover a device in a namespace call Mdns.Client.query(namespace \\ "_services._dns-sd._udp.local"). Compliant devices will respond with a DNS response. Mdns.Client will send notify the Mdns Registry You can add an event handler by calling Mdns.EventManager.add_handler or Mdns.EventManager.register. This will send a message to the process that called Mdns.EventManager.add_handler or Mdns.EventManager.register. You should handle this message in a handle_info callback.

Calling Mdns.Client.query("_googlecast._tcp.local")

assuming you have a Chromecast on your network, an event is broadcast that looks like this

{:"_googlecast._tcp.local",
    %Mdns.Client.Device{
        domain: "CRT-Labs.local",
        ip: {192, 168, 1, 138},
        payload: %{
            "bs" => "FA8FCA79C426",
            "ca" => "4101",
            "fn" => "CRT-Labs",
            "ic" => "/setup/icon.png",
            "id" => "e0617de7e2df63476fab257c8327ef3b",
            "md" => "Chromecast",
            "rm" => "E81C9A486980AA48",
            "rs" => "",
            "st" => "0",
            "ve" => "05"
        },
        services: [
            "CRT-Labs._googlecast._tcp.local"
        ]
    }
}

After calling Mdns.Client.query("_ssh._tcp.local") in addition to the Chromecast call above, Mdns.Client.devices will return a devices map similar to this. Assuming you have a device on the network that supports ssh.

%{"_googlecast._tcp.local": [
        %Mdns.Client.Device{
            domain: "CRT-Labs.local",
            ip: {192, 168, 1, 138},
            payload: %{
                "bs" => "FA8FCA79C426",
                "ca" => "4101",
                "fn" => "CRT-Labs",
                "ic" => "/setup/icon.png",
                "id" => "e0617de7e2df63476fab257c8327ef3b",
                "md" => "Chromecast",
                "rm" => "E81C9A486980AA48",
                "rs" => "",
                "st" => "0",
                "ve" => "05"
            },
            services: [
                "CRT-Labs._googlecast._tcp.local"
            ]
        }
    ],
    "_ssh._tcp.local": [
        %Mdns.Client.Device{
            domain: "pc-6105006P.local",
            ip: {192, 168, 1, 26},
            payload: nil,
            services: [
                "pc-6105006P._ssh._tcp.local"
            ]
        }
    ],
    other: []
}

mdns's People

Contributors

eduardo-cunha-bose avatar entone avatar fazibear avatar fhunleth avatar matthew-myers-boseprofessional avatar mickel8 avatar mobileoverlord avatar nuno-silva-bose avatar pragdave avatar spikegrobstein avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mdns's Issues

[Question] Why mDNS query responses are dropped?

Hi!
I am trying to use your library to resolve mDNS ICE candidates that different browsers send while establishing the connection.

When ICE candidate ends with .local I am trying to resolve it by

Mdns.Client.query("some-string.local")

Before it I have done

Mdns.Client.start()
Mdns.Client.register()

While executing the above query I can see in wireshark:
image

So there is a queary and a response.

The library logs it by

13:20:20.283 [debug] mDNS got response: %DNS.Record{anlist: [%DNS.Resource{bm: [], class: 32769, cnt: 0, data: <<192, 12, 0, 4, 0, 0, 0, 8>>, domain: '3b36c03b-71c2-4fa6-b683-64275f83fc0e.local', func: false, tm: :undefined, ttl: 120, type: 47}], arlist: [%DNS.Resource{bm: [], class: 32769, cnt: 0, data: <<42, 1, 17, 223, 3, 113, 137, 0, 178, 64, 52, 238, 125, 138, 184, 48>>, domain: '3b36c03b-71c2-4fa6-b683-64275f83fc0e.local', func: false, tm: :undefined, ttl: 120, type: :aaaa}], header: %DNS.Header{aa: true, id: 0, opcode: :query, pr: false, qr: true, ra: false, rcode: 0, rd: false, tc: false}, nslist: [], qdlist: []}

13:20:20.283 [debug] mDNS got response: %DNS.Record{anlist: [%DNS.Resource{bm: [], class: 32769, cnt: 0, data: <<192, 12, 0, 4, 0, 0, 0, 8>>, domain: '3b36c03b-71c2-4fa6-b683-64275f83fc0e.local', func: false, tm: :undefined, ttl: 120, type: 47}], arlist: [%DNS.Resource{bm: [], class: 32769, cnt: 0, data: <<42, 1, 17, 223, 3, 113, 137, 0, 178, 64, 52, 238, 125, 138, 184, 48>>, domain: '3b36c03b-71c2-4fa6-b683-64275f83fc0e.local', func: false, tm: :undefined, ttl: 120, type: :aaaa}], header: %DNS.Header{aa: true, id: 0, opcode: :query, pr: false, qr: true, ra: false, rcode: 0, rd: false, tc: false}, nslist: [], qdlist: []}

but I don't get any notifications which is probably caused by this code

mdns/lib/mdns/client.ex

Lines 115 to 127 in 83c6871

devices =
Enum.reduce(state.queries, %{:other => []}, fn query, acc ->
cond do
Enum.any?(device.services, fn service -> String.ends_with?(service, query) end) ->
{namespace, devices} = create_namespace_devices(query, device, acc, state)
Mdns.EventManager.notify({namespace, device})
Logger.debug("mDNS device: #{inspect({namespace, device})}")
devices
true ->
Map.merge(acc, state.devices)
end
end)

So my question is why this response is filtered out and I am not notified about it?

Also tried to implement mDNS lookup in C in this way

  char *rest;
  char *candidate_cpy = (char *)calloc(1024, sizeof(char));
  char *prefix = strtok_r(candidate, " ", &rest);
  char *component = strtok_r(NULL, " ", &rest);
  char *proto = strtok_r(NULL, " ", &rest);
  char *prio = strtok_r(NULL, " ", &rest);
  char *addr = strtok_r(NULL, " ", &rest);
  
  char *addr_postfix_ptr = strrchr(addr, '.');
  // check if addr ends with .local
  if (addr_postfix_ptr && !strcmp(addr_postfix_ptr, ".local")) {
    
    struct addrinfo *addrinfo;
    int ret = getaddrinfo(addr, NULL, NULL, &addrinfo);
    fprintf(stderr, "ret %d for %s\n", ret, addr);
    }

but this piece of code doesn't make any mDNS queries ๐Ÿค” It does only DNS ones so I suppose I am missing something about mDNS protocol itself?

Question - How to serve a record with a payload?

I've been playing with Mdns.Server and Mdns.Client for a little while, but I can't figure out how to serve a record with a payload. I want to do something like the googlecast example in the Readme:
Is that possible with this mdns Elixir library?

{:"_googlecast._tcp.local",
    %Mdns.Client.Device{
        domain: "CRT-Labs.local",
        ip: {192, 168, 1, 138},
        payload: %{
            "bs" => "FA8FCA79C426",
            "ca" => "4101",
            "fn" => "CRT-Labs",
            "ic" => "/setup/icon.png",
            "id" => "e0617de7e2df63476fab257c8327ef3b",
            "md" => "Chromecast",
            "rm" => "E81C9A486980AA48",
            "rs" => "",
            "st" => "0",
            "ve" => "05"
        },
        services: [
            "CRT-Labs._googlecast._tcp.local"
        ]
    }
}

Resolve domain name

When I'm start a server with following records:

[
  # create domain for an ip
  %Mdns.Server.Service{domain: "somedomain.local", data: :ip, ttl: 450, type: :a},

  # make service discoverable
  %Mdns.Server.Service{domain: "_services._dns-sd._udp.local",data: "_ssh._tcp.local",ttl: 4500, type: :ptr},

  # register ssh service
  %Mdns.Server.Service{domain: "_ssh._tcp.local",data: "SOME NAME._ssh._tcp.local",ttl: 4500, type: :ptr},

  # point service to our domain and port (22)
  %Mdns.Server.Service{domain: "SOME NAME._ssh._tcp.local",data: {0,0,22, 'somedomain.local'},ttl: 4500,type: :srv},

  # empty txt service (some tools expext that)
  %Mdns.Server.Service{domain: "SOME NAME._ssh._tcp.local",data: [],ttl: 4500,type: :txt})
] |> Enum.each(&Mdns.Server.add_service/1)

Tools like Avahi Discovery, Avahi SSH Server Browse, mdns-scan works fine.

But when I'm trying to find device with Mdns.Client the domain is nil.

After a little research this condition https://github.com/rosetta-home/mdns/blob/master/lib/mdns/server.ex#L106 prevent to send :a record witch Mdns.Client is looking for. On the other side other tools resolve domain correctly.

Should Mdns.Server always send :a record od Mds.Client should resolve domain in a different way?

Perhaps too general matching responses?

I'm not too familiar with mdns, so perhaps this is how it is supposed to work, but...

I have a Node.js service that I'm advertising as trace-appliance._trap-tracer._udp.local.

In my Elixir app, I query for that string.

Tracing the handle_info callbacks, I see messages such as those down below.

The first is a correct response, giving the IP and port of my service.

The others however are responses for the AFP service. They've been selected because the
list of services they contain includes what I'm guessing are all the services advertised by my box.

Looking at the code, the mdns client seems to accept a message if any of the services it contains is a substring of the query.

Am I using it incorrectly?

Dave

==================================================
{:"trace-appliance._trap-tracer._udp.local",
 %Mdns.Client.Device{
   domain: "laptop.lan.local",
   ip: {192, 168, 86, 173},
   payload: %{},
   port: 51516,
   services: ["trace-appliance._trap-tracer._udp.local",
    "_trap-tracer._udp.local"]
 }}
==================================================
{:"trace-appliance._trap-tracer._udp.local",
 %Mdns.Client.Device{
   domain: "laptop.local",
   ip: {192, 168, 86, 173},
   payload: %{},
   port: 7000,
   services: [
     <<192, 56, 0, 4, 64, 0, 0, 8>>,
     "laptop.local",
     <<192, 12, 0, 5, 0, 0, 128, 0, 64>>,
     "laptop._airplay._tcp.local",
     "trace-appliance._trap-tracer._udp.local",
     "_trap-tracer._udp.local"
   ]
 }}
==================================================
{:"trace-appliance._trap-tracer._udp.local",
 %Mdns.Client.Device{
   domain: "laptop.local",
   ip: {192, 168, 86, 173},
   payload: %{},
   port: 7000,
   services: [
     <<192, 66, 0, 4, 64, 0, 0, 8>>,
     "laptop.local",
     <<192, 12, 0, 5, 0, 0, 128, 0, 64>>,
     "F01898668893@laptop._raop._tcp.local", 
     <<192, 56, 0, 4, 64, 0, 0, 8>>,
     "laptop._airplay._tcp.local",
     "trace-appliance._trap-tracer._udp.local",
     "_trap-tracer._udp.local"
   ]
 }}

Service discovery example

I'd like to advertise an ssh service so that a laptop running on the same LAN can discover the device. I've tried avahi-browse _ssh._tcp on my Linux laptop and made several attempts with registering a service. Is there an example somewhere on how to do this? or something similar that I could modify? I saw that you could register PTR and TXT records in the readme, but my attempts to use those have failed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.