ZeroConf: a Pure Ruby multicast DNS client and server
ZeroConf is a multicast DNS client and server written in pure Ruby. Use it to find multicast services, or advertise your own!
Client Example
I've got some Elgato Key lights that can be controlled via the network.
In this example, we're going to use ZeroConf to find them on the network,
then we'll connect with net/http
and control them.
Lets start by getting a list of Multicast DNS services on the network:
all_services = ZeroConf.find_services
service = all_services.grep(/_elg/).first # => "_elg._tcp.local"
We've found the Elgato service name on the network, next lets get the host names and connection information:
addr_infos = ZeroConf.find_addrinfos(service)
I have two lights on my network, and both of them have an IPv4 and IPv6 addresses, so we get back an array with 4 tuples:
[["elgato-key-light-2d93.local", #<Addrinfo: 10.0.1.249:9123 (elgato-key-light-2d93.local)>],
["elgato-key-light-2d93.local", #<Addrinfo: [fe80::3e6a:9dff:fe19:b313]:9123 (elgato-key-light-2d93.local)>],
["elgato-key-light-48c6.local", #<Addrinfo: 10.0.1.151:9123 (elgato-key-light-48c6.local)>],
["elgato-key-light-48c6.local", #<Addrinfo: [fe80::3e6a:9dff:fe19:3a99]:9123 (elgato-key-light-48c6.local)>]]
We can connect either via IP address or host name. In this example, I'm just going to get the unique host names and ports, connect to those, and shut off the lights.
require "uri"
require "net/http"
require "json"
# Get unique host / port
connection_info = addr_infos.uniq(&:first).map { |host, addr| [host, addr.ip_port] }
# Turn the lights off
connection_info.each { |host, port|
req = Net::HTTP::Put.new("/elgato/lights")
req.body = { "numberOfLights": 1, "lights": [ { "on": 0 } ] }.to_json
Net::HTTP.start(host, port) { |http| http.request(req) }
}
Here is the whole script:
require "zeroconf"
require "uri"
require "net/http"
require "json"
# Find all services
all_services = ZeroConf.find_services
# Look for Elgato
service = all_services.grep(/_elg/).first || raise # => "_elg._tcp.local"
# Get addrinfo objects associated with that service
addr_infos = ZeroConf.find_addrinfos(service)
# Get unique host / port
connection_info = addr_infos.uniq(&:first).map { |host, addr| [host, addr.ip_port] }
# Turn the lights off
connection_info.each { |host, port|
req = Net::HTTP::Put.new("/elgato/lights")
req.body = { "numberOfLights": 1, "lights": [ { "on": 0 } ] }.to_json
Net::HTTP.start(host, port) { |http| http.request(req) }
}
Once you've discovered the host names of the devices, you really don't need to use the ZeroConf library anymore. You can just hardcode the host names in your script. If the host names ever change, just rediscover them with ZeroConf.
Server Example
Advertising a service requires the service name, the hostname, and the port you want to advertise on the network.
For example, we want to advertise an HTTP service running on port 8080, and we want the hostname to be "test-hostname.local".
Use the following code (note that the hostname should omit .local
):
require "zeroconf"
ZeroConf.service "_http._tcp.local.", 8080, "test-hostname"
While this script is running, you should be able to ping test-hostname.local
.
ZeroConf.service
can be run in a thread, so we could combine it with WEBrick
to run an HTTP server and simultaneously advertise the server:
require "zeroconf"
require 'webrick'
port = 8080
host = "test-hostname"
Thread.new { ZeroConf.service "_http._tcp.local.", port, host }
server = WEBrick::HTTPServer.new(:Port => port,
:SSLEnable => false,
:ServerAlias => host + ".local")
server.mount_proc '/' do |req, res|
res.body = 'Hello, world!'
end
trap 'INT' do server.shutdown end
server.start