-
Notifications
You must be signed in to change notification settings - Fork 4
/
mdns-proxy.rb
executable file
·133 lines (106 loc) · 3.33 KB
/
mdns-proxy.rb
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
123
124
125
126
127
128
129
130
131
132
133
#!/usr/bin/env ruby
require 'rubygems'
require 'eventmachine'
require 'resolv'
require 'socket'
require 'etc'
BIND_ADDRESS = '127.0.0.1' # you probably want to bind to your LAN address
BIND_PORT = 53 # ports lower than 1024 require superuser privileges
PROXY_DOMAIN = 'vpn' # accept queries for *.vpn
LOOKUP_DOMAIN = 'local' # proxy them as queries for *.local
module Server
UDP_TRUNCATION_SIZE = 512
def initialize
set_comm_inactivity_timeout 15
end
def receive_data(data)
port, ip = Socket.unpack_sockaddr_in(get_peername)
query = Resolv::DNS::Message::decode(data)
response = Response.new(self, query, "#{ip}:#{port}")
end
def respond(answer)
data = answer.encode
if (data.size > UDP_TRUNCATION_SIZE)
answer.tc = 1
data = answer.encode[0, UDP_TRUNCATION_SIZE]
end
send_data(data)
end
end
class Response
def initialize(server, query, peer)
@server = server
@peer = peer
# Setup answer
@answer = Resolv::DNS::Message::new(query.id)
@answer.qr = 1 # 0 = Query, 1 = Response
@answer.opcode = query.opcode # Type of Query; copy from query
@answer.aa = 1 # Is this an authoritative response: 0 = No, 1 = Yes
@answer.rd = query.rd # Is Recursion Desired, copied from query
@answer.ra = 0 # Does name server support recursion: 0 = No, 1 = Yes
@answer.rcode = 0 # Response code: 0 = No errors
@pending = []
query.each_question do |question, resource_class| # There may be multiple questions per query
puts "Received query for #{question.to_s} from #{@peer}"
next unless question.to_s =~ /^([^\.]+)\.([^\.]+)\.?$/
name, domain = $1, $2
next unless domain == PROXY_DOMAIN
@pending << Resolver.new(self, name, domain)
end
ping
end
def ping
send_answer if ready?
end
def ready?
@pending.all? {|r| r.ready?}
end
def send_answer
@pending.each do |resolver|
if resolver.success?
resource = Resolv::DNS::Resource::IN::A.new(resolver.answer_ip)
@answer.add_answer(resolver.answer_name + '.', 300, resource)
puts "Sending #{resolver.answer_name} = #{resolver.answer_ip} to #{@peer}"
else
@answer.rcode = 3 # nxdomain
puts "Sending NXDOMAIN to #{@peer}"
end
end
@server.respond(@answer)
end
end
module ProcessWatcher
def initialize(resolv)
@resolv = resolv
end
def process_exited
@resolv.ping
end
end
class Resolver
attr_reader :ready, :success, :answer_name, :answer_ip
alias_method :ready?, :ready
alias_method :success?, :success
def initialize(response, name, domain)
@response = response
@answer_name = "#{name}.#{domain}"
@ready = false
EventMachine.system('avahi-resolve', '-4n', "#{name}.#{LOOKUP_DOMAIN}") do |output, status|
line = output.lines.first
@answer_ip = line.chomp.split("\t").last if line
@success = !line.nil?
@ready = true
@response.ping
end
end
end
def drop_privs(user, group)
uid = Etc.getpwnam(user).uid
gid = Etc.getgrnam(group).gid
Process::Sys.setgid(gid)
Process::Sys.setuid(uid)
end
EventMachine.run do
EventMachine.open_datagram_socket(BIND_ADDRESS, BIND_PORT, Server)
drop_privs('nobody', 'nogroup') if Process::Sys.getuid == 0
end