-
-
Save adam12/7f5a87e5940a9c0f6b334c6d432bb76e to your computer and use it in GitHub Desktop.
Sample mDNS client in Ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# mDNS client | |
# | |
# Usage: ruby script.rb "_http._tcp.local." | |
require "socket" | |
require "ipaddr" | |
require "fcntl" | |
require "resolv" | |
module DNSSD | |
MDNS_PORT = 5353 | |
MDNS_UNICAST_RESPONSE = 0x8000 | |
class Query | |
attr_reader :name | |
TypeValue = 12 # PTR | |
ClassValue = 1 | MDNS_UNICAST_RESPONSE | |
def initialize name | |
@name = name | |
end | |
end | |
def self.browse name, &blk | |
send_query [Query.new(name)], &blk | |
end | |
private_class_method def self.open_client_sockets port | |
addrs = Socket.getifaddrs | |
addrs.select { |ifa| | |
ifa.addr && | |
(ifa.flags & Socket::IFF_UP > 0) && # must be up | |
(ifa.flags & Socket::IFF_MULTICAST > 0) && # must have multicast | |
(ifa.flags & Socket::IFF_LOOPBACK == 0) && # must not be loopback | |
(ifa.flags & Socket::IFF_POINTOPOINT == 0) # must not be pointopoint | |
}.select { |ifa| ifa.addr.ipv4? }.map { |ifa| | |
open_ipv4(ifa.addr, port) | |
} | |
end | |
private_class_method def self.send_query queries | |
sockets = open_client_sockets 0 | |
sockets.each { |socket| multiquery_send socket, queries, 0 } | |
loop do | |
readers, = IO.select(sockets, [], []) | |
readers.each do |reader| | |
yield query_recv(reader) | |
end | |
end | |
end | |
private_class_method def self.open_ipv4 saddr, port | |
sock = UDPSocket.new | |
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) | |
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true) | |
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, true) | |
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_LOOP, true) | |
setup_ipv4 sock, saddr, port | |
sock | |
end | |
private_class_method def self.setup_ipv4 sock, saddr, port | |
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, | |
IPAddr.new("224.0.0.251").hton + IPAddr.new(saddr.ip_address).hton) | |
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_IF, IPAddr.new(saddr.ip_address).hton) | |
sock.bind saddr.ip_address, port | |
flags = sock.fcntl(Fcntl::F_GETFL, 0) | |
sock.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK | flags) | |
end | |
private_class_method def self.multiquery_send sock, queries, query_id | |
query = Resolv::DNS::Message.new query_id | |
queries.each { |q| query.add_question q.name, q.class } | |
multicast_send sock, query.encode | |
end | |
private_class_method def self.multicast_send sock, query | |
dest = Addrinfo.new(["AF_INET", MDNS_PORT, nil, "224.0.0.251"]) | |
sock.send(query, 0, dest) | |
end | |
private_class_method def self.query_recv sock | |
buf, = sock.recvfrom 2048 | |
Resolv::DNS::Message.decode(buf) | |
end | |
end | |
if __FILE__ == $0 | |
DNSSD.browse ARGV[0] || "_http._tcp.local." do |recv| | |
name = nil | |
address = nil | |
recv.additional.each do |_, _, data| | |
case data | |
when Resolv::DNS::Resource::IN::SRV | |
name = data.target | |
when Resolv::DNS::Resource::IN::A | |
address = data.address | |
end | |
end | |
p FOUND: [name, address] | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment