#!/usr/bin/env ruby # Copyright (c) 2012, Matthias Schiffer # All rights reserved. # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'set' require 'socket' require 'net/dns' require 'net/dns/packet' ### Config ### $domain = 'mesh.ffhl' $port = 53 $ttl = 10 $ns = ['mesh.ffhl. 3600 IN NS paul.ffhl.'] $soa = 'mesh.ffhl. 3600 IN SOA paul.ffhl. freifunk\.luebeck.asta.uni-luebeck.de. 1 3600 180 600 60' ### Config end ### def soaRecord record = Net::DNS::RR::SOA.new $soa return Net::DNS::RR::SOA.new(:name => record.name, :ttl => record.ttl, :mname => record.mname, :rname => record.rname, :refresh => record.refresh, :retry => record.retry, :expire => record.expire, :minimum => record.minimum, :serial => Time.now.to_i) end run = true sock = UDPSocket.new Socket::AF_INET6 sock.setsockopt Socket::Option.bool(:INET6, :IPV6, :V6ONLY, false) sock.setsockopt Socket::Option.bool(:INET, :IP, :PKTINFO, true) sock.setsockopt Socket::Option.bool(:INET6, :IPV6, :RECVPKTINFO, true) sock.bind('::', $port) while run do data, sendaddr, rflags, *controls = sock.recvmsg Thread.new(data, sendaddr, controls) do |data, sendaddr, controls| pktinfo = nil controls.each do |c| pktinfo = c if c.cmsg_is?(:IP, :PKTINFO) or c.cmsg_is?(:IPV6, :PKTINFO) end return if pktinfo.nil? query = Net::DNS::Packet::parse(data) return unless query.header.query? packet = Net::DNS::Packet.new packet.header.id = query.header.id packet.header.opCode = query.header.opCode packet.header.qr = 1 packet.header.aa = 1 packet.header.rd = query.header.recursive? query.question.each { |q| packet.question << q } if query.header.opCode == Net::DNS::Header::QUERY query.question.each do |q| next unless q.qClass.to_i == Net::DNS::IN if q.qName == $domain+'.' packet.answer << soaRecord if q.qType.to_i == Net::DNS::SOA or q.qType.to_i == Net::DNS::ANY $ns.each { |line| packet.answer << Net::DNS::RR::NS.new(line) } if q.qType.to_i == Net::DNS::NS or q.qType.to_i == Net::DNS::ANY else host, qdomain = q.qName.split('.', 2) next unless qdomain == $domain+'.' begin addresses = Set.new begin Socket.getaddrinfo(host+'.local.', nil, :INET).each { |addr| addresses << [addr[0], addr[3]] } rescue end begin Socket.getaddrinfo(host+'.local.', nil, :INET6).each { |addr| addresses << [addr[0], addr[3]] } rescue end raise if addresses.empty? if q.qType.to_i == Net::DNS::A or q.qType.to_i == Net::DNS::ANY addresses.each do |addr| packet.answer << Net::DNS::RR::A.new(:name => q.qName, :ttl => $ttl, :cls => Net::DNS::IN, :type => Net::DNS::A, :address => addr[1]) if addr[0] == "AF_INET" end end if q.qType.to_i == Net::DNS::AAAA or q.qType.to_i == Net::DNS::ANY addresses.each do |addr| packet.answer << Net::DNS::RR::AAAA.new(:name => q.qName, :ttl => $ttl, :cls => Net::DNS::IN, :type => Net::DNS::AAAA, :address => addr[1]) if addr[0] == "AF_INET6" end end rescue packet.header.rCode = Net::DNS::Header::RCode::NAME end end end else packet.header.rCode = Net::DNS::Header::RCode::NOTIMPLEMENTED end if packet.answer.empty? packet.authority = [soaRecord] else $ns.each { |line| packet.authority << Net::DNS::RR::NS.new(line) } end sock.sendmsg(packet.data, 0, sendaddr, pktinfo) end end