summaryrefslogtreecommitdiffstats
path: root/mdns-resolver
blob: 9d3f4b2cba39deec53813304df9f5445b1e0678c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/usr/bin/env ruby

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.opCode == Net::DNS::Header::QUERY
    return unless query.header.query?

    packet = Net::DNS::Packet.new
    packet.header.id = query.header.id
    packet.header.opCode = Net::DNS::Header::QUERY
    packet.header.qr = 1
    packet.header.aa = 1
    packet.header.rd = query.header.recursive?

    query.question.each do |q|
      packet.question << 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

    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