Tizen 2.1 base
[platform/upstream/hplip.git] / base / mdns.py
1 # -*- coding: utf-8 -*-
2 #
3 # (c) Copyright 2003-2007 Hewlett-Packard Development Company, L.P.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 #
19 # Author: Don Welch
20 #
21
22 # RFC 1035
23
24 # Std Lib
25 import sys
26 import time
27 import socket
28 import select
29 import struct
30 import random
31 import re
32 import cStringIO
33
34 # Local
35 from g import *
36 import utils
37
38 MAX_ANSWERS_PER_PACKET = 24
39
40 QTYPE_A = 1
41 QTYPE_TXT = 16
42 QTYPE_SRV = 33
43 QTYPE_AAAA = 28
44 QTYPE_PTR = 12
45
46 QCLASS_IN = 1
47
48
49 def read_utf8(offset, data, l):
50     return offset+l, data[offset:offset+l].decode('utf-8')
51
52 def read_data(offset, data, l):
53     return offset+l, data[offset:offset+l]
54
55 def read_data_unpack(offset, data, fmt):
56     l = struct.calcsize(fmt)
57     return offset+l, struct.unpack(fmt, data[offset:offset+l])
58
59 def read_name(offset, data):
60     result = ''
61     off = offset
62     next = -1
63     first = off
64
65     while True:
66         l = ord(data[off])
67         off += 1
68
69         if l == 0:
70             break
71
72         t = l & 0xC0
73
74         if t == 0x00:
75             off, utf8 = read_utf8(off, data, l)
76             result = ''.join([result, utf8, '.'])
77
78         elif t == 0xC0:
79             if next < 0:
80                 next = off + 1
81
82             off = ((l & 0x3F) << 8) | ord(data[off])
83
84             if off >= first:
85                 log.error("Bad domain name (circular) at 0x%04x" % off)
86                 break
87
88             first = off
89
90         else:
91             log.error("Bad domain name at 0x%04x" % off)
92             break
93
94     if next >= 0:
95         offset = next
96
97     else:
98         offset = off
99
100     return offset, result
101
102
103 def write_name(packet, name):
104     for p in name.split('.'):
105         utf8_string = p.encode('utf-8')
106         packet.write(struct.pack('!B', len(utf8_string)))
107         packet.write(utf8_string)
108
109
110 def create_outgoing_packets(answers):
111     index = 0
112     num_questions = 1
113     first_packet = True
114     packets = []
115     packet = cStringIO.StringIO()
116     answer_record = cStringIO.StringIO()
117
118     while True:
119         packet.seek(0)
120         packet.truncate()
121
122         num_answers = len(answers[index:index+MAX_ANSWERS_PER_PACKET])
123
124         if num_answers == 0 and num_questions == 0:
125             break
126
127         flags = 0x0200 # truncated
128         if len(answers) - index <= MAX_ANSWERS_PER_PACKET:
129             flags = 0x0000 # not truncated
130
131         # ID/FLAGS/QDCOUNT/ANCOUNT/NSCOUNT/ARCOUNT
132         packet.write(struct.pack("!HHHHHH", 0x0000, flags, num_questions, num_answers, 0x0000, 0x0000))
133
134         if num_questions:
135             # QNAME
136             write_name(packet, "_pdl-datastream._tcp.local") # QNAME
137             packet.write(struct.pack("!B", 0x00))
138
139             # QTYPE/QCLASS
140             packet.write(struct.pack("!HH", QTYPE_PTR, QCLASS_IN))
141
142         first_record = True
143         for d in answers[index:index+MAX_ANSWERS_PER_PACKET]:
144             answer_record.seek(0)
145             answer_record.truncate()
146
147             # NAME
148             if not first_packet and first_record:
149                 first_record = False
150                 write_name(answer_record, "_pdl-datastream._tcp.local")
151                 answer_record.write(struct.pack("!B", 0x00))
152             else:
153                 answer_record.write(struct.pack("!H", 0xc00c)) # Pointer
154
155             # TYPE/CLASS
156             answer_record.write(struct.pack("!HH", QTYPE_PTR, QCLASS_IN))
157
158             # TTL
159             answer_record.write(struct.pack("!I", 0xffff))
160             rdlength_pos = answer_record.tell()
161
162             # RDLENGTH
163             answer_record.write(struct.pack("!H", 0x0000)) # (adj later)
164
165             # RDATA
166             write_name(answer_record, d)
167             answer_record.write(struct.pack("!H", 0xc00c)) # Ptr
168
169             # RDLENGTH
170             rdlength = answer_record.tell() - rdlength_pos - 2
171             answer_record.seek(rdlength_pos)
172             answer_record.write(struct.pack("!H", rdlength))
173
174             answer_record.seek(0)
175             packet.write(answer_record.read())
176
177         packets.append(packet.getvalue())
178
179         index += 20
180
181         if first_packet:
182             num_questions = 0
183             first_packet = False
184
185     return packets
186
187
188
189 def detectNetworkDevices(ttl=4, timeout=10):
190     mcast_addr, mcast_port ='224.0.0.251', 5353
191     found_devices = {}
192     answers = []
193
194     try:
195         s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
196         x = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
197         x.connect(('1.2.3.4', 56))
198         intf = x.getsockname()[0]
199         x.close()
200
201         s.setblocking(0)
202         ttl = struct.pack('B', ttl)
203     except socket.error:
204         log.error("Network error")
205         return {}
206
207     try:
208         s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
209         s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
210     except (AttributeError, socket.error):
211         pass
212
213     try:
214         s.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, ttl)
215         s.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(intf) + socket.inet_aton('0.0.0.0'))
216         s.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP ,1)
217     except Exception, e:
218         log.error("Unable to setup multicast socket for mDNS: %s" % e)
219         return {}
220
221     now = time.time()
222     next = now
223     last = now + timeout
224     delay = 1
225
226     while True:
227         now = time.time()
228
229         if now > last:
230             break
231
232         if now >= next:
233             try:
234                 for p in create_outgoing_packets(answers):
235                     log.debug("Outgoing: (%d)" % len(p))
236                     log.log_data(p, width=16)
237                     s.sendto(p, 0, (mcast_addr, mcast_port))
238
239             except socket.error, e:
240                 log.error("Unable to send broadcast DNS packet: %s" % e)
241
242             next += delay
243             delay *= 2
244
245         update_spinner()
246
247         r, w, e = select.select([s], [], [s], 0.5)
248
249         if not r:
250             continue
251
252         data, addr = s.recvfrom(16384)
253
254         if data:
255             update_spinner()
256             y = {'num_devices' : 1, 'num_ports': 1, 'product_id' : '', 'mac': '',
257                  'status_code': 0, 'device2': '0', 'device3': '0', 'note': ''}
258
259             log.debug("Incoming: (%d)" % len(data))
260             log.log_data(data, width=16)
261
262             offset = 0
263             offset, (id, flags, num_questions, num_answers, num_authorities, num_additionals) = \
264                 read_data_unpack(offset, data, "!HHHHHH")
265
266             log.debug("Response: ID=%d FLAGS=0x%x Q=%d A=%d AUTH=%d ADD=%d" %
267                 (id, flags, num_questions, num_answers, num_authorities, num_additionals))
268
269             for question in range(num_questions):
270                 update_spinner()
271                 offset, name = read_name(offset, data)
272                 offset, (typ, cls) = read_data_unpack(offset, data, "!HH")
273                 log.debug("Q: %s TYPE=%d CLASS=%d" % (name, typ, cls))
274
275             fmt = '!HHiH'
276             for record in range(num_answers + num_authorities + num_additionals):
277                 update_spinner()
278                 offset, name = read_name(offset, data)
279                 offset, info = read_data_unpack(offset, data, "!HHiH")
280
281                 if info[0] == QTYPE_A: # ipv4 address
282                     offset, result = read_data(offset, data, 4)
283                     ip = '.'.join([str(ord(x)) for x in result])
284                     log.debug("A: %s" % ip)
285                     y['ip'] = ip
286
287                 elif info[0] == QTYPE_PTR: # PTR
288                     offset, name = read_name(offset, data)
289                     log.debug("PTR: %s" % name)
290                     y['mdns'] = name
291                     answers.append(name.replace("._pdl-datastream._tcp.local.", ""))
292
293                 elif info[0] == QTYPE_TXT:
294                     offset, name = read_data(offset, data, info[3])
295                     txt, off = {}, 0
296
297                     while off < len(name):
298                         l = ord(name[off])
299                         off += 1
300                         result = name[off:off+l]
301
302                         try:
303                             key, value = result.split('=')
304                             txt[key] = value
305                         except ValueError:
306                             pass
307
308                         off += l
309
310                     log.debug("TXT: %s" % repr(txt))
311                     try:
312                         y['device1'] = "MFG:Hewlett-Packard;MDL:%s;CLS:PRINTER;" % txt['ty']
313                     except KeyError:
314                         log.debug("NO ty Key in txt: %s" % repr(txt))
315
316                     if 'note' in txt:
317                         y['note'] = txt['note']
318
319                 elif info[0] == QTYPE_SRV:
320                     offset, (priority, weight, port) = read_data_unpack(offset, data, "!HHH")
321                     ttl = info[3]
322                     offset, server = read_name(offset, data)
323                     log.debug("SRV: %s TTL=%d PRI=%d WT=%d PORT=%d" % (server, ttl, priority, weight, port))
324                     y['hn'] = server.replace('.local.', '')
325
326                 elif info[0] == QTYPE_AAAA: # ipv6 address
327                     offset, result = read_data(offset, data, 16)
328                     log.debug("AAAA: %s" % repr(result))
329
330                 else:
331                     log.error("Unknown DNS record type (%d)." % info[0])
332                     break
333
334         found_devices[y['ip']] = y
335
336     log.debug("Found %d devices" % len(found_devices))
337
338     return found_devices
339
340