1 # -*- coding: utf-8 -*-
3 # (c) Copyright 2003-2007 Hewlett-Packard Development Company, L.P.
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.
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.
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
38 MAX_ANSWERS_PER_PACKET = 24
49 def read_utf8(offset, data, l):
50 return offset+l, data[offset:offset+l].decode('utf-8')
52 def read_data(offset, data, l):
53 return offset+l, data[offset:offset+l]
55 def read_data_unpack(offset, data, fmt):
56 l = struct.calcsize(fmt)
57 return offset+l, struct.unpack(fmt, data[offset:offset+l])
59 def read_name(offset, data):
75 off, utf8 = read_utf8(off, data, l)
76 result = ''.join([result, utf8, '.'])
82 off = ((l & 0x3F) << 8) | ord(data[off])
85 log.error("Bad domain name (circular) at 0x%04x" % off)
91 log.error("Bad domain name at 0x%04x" % off)
100 return offset, result
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)
110 def create_outgoing_packets(answers):
115 packet = cStringIO.StringIO()
116 answer_record = cStringIO.StringIO()
122 num_answers = len(answers[index:index+MAX_ANSWERS_PER_PACKET])
124 if num_answers == 0 and num_questions == 0:
127 flags = 0x0200 # truncated
128 if len(answers) - index <= MAX_ANSWERS_PER_PACKET:
129 flags = 0x0000 # not truncated
131 # ID/FLAGS/QDCOUNT/ANCOUNT/NSCOUNT/ARCOUNT
132 packet.write(struct.pack("!HHHHHH", 0x0000, flags, num_questions, num_answers, 0x0000, 0x0000))
136 write_name(packet, "_pdl-datastream._tcp.local") # QNAME
137 packet.write(struct.pack("!B", 0x00))
140 packet.write(struct.pack("!HH", QTYPE_PTR, QCLASS_IN))
143 for d in answers[index:index+MAX_ANSWERS_PER_PACKET]:
144 answer_record.seek(0)
145 answer_record.truncate()
148 if not first_packet and first_record:
150 write_name(answer_record, "_pdl-datastream._tcp.local")
151 answer_record.write(struct.pack("!B", 0x00))
153 answer_record.write(struct.pack("!H", 0xc00c)) # Pointer
156 answer_record.write(struct.pack("!HH", QTYPE_PTR, QCLASS_IN))
159 answer_record.write(struct.pack("!I", 0xffff))
160 rdlength_pos = answer_record.tell()
163 answer_record.write(struct.pack("!H", 0x0000)) # (adj later)
166 write_name(answer_record, d)
167 answer_record.write(struct.pack("!H", 0xc00c)) # Ptr
170 rdlength = answer_record.tell() - rdlength_pos - 2
171 answer_record.seek(rdlength_pos)
172 answer_record.write(struct.pack("!H", rdlength))
174 answer_record.seek(0)
175 packet.write(answer_record.read())
177 packets.append(packet.getvalue())
189 def detectNetworkDevices(ttl=4, timeout=10):
190 mcast_addr, mcast_port ='224.0.0.251', 5353
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]
202 ttl = struct.pack('B', ttl)
204 log.error("Network error")
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):
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)
218 log.error("Unable to setup multicast socket for mDNS: %s" % e)
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))
239 except socket.error, e:
240 log.error("Unable to send broadcast DNS packet: %s" % e)
247 r, w, e = select.select([s], [], [s], 0.5)
252 data, addr = s.recvfrom(16384)
256 y = {'num_devices' : 1, 'num_ports': 1, 'product_id' : '', 'mac': '',
257 'status_code': 0, 'device2': '0', 'device3': '0', 'note': ''}
259 log.debug("Incoming: (%d)" % len(data))
260 log.log_data(data, width=16)
263 offset, (id, flags, num_questions, num_answers, num_authorities, num_additionals) = \
264 read_data_unpack(offset, data, "!HHHHHH")
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))
269 for question in range(num_questions):
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))
276 for record in range(num_answers + num_authorities + num_additionals):
278 offset, name = read_name(offset, data)
279 offset, info = read_data_unpack(offset, data, "!HHiH")
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)
287 elif info[0] == QTYPE_PTR: # PTR
288 offset, name = read_name(offset, data)
289 log.debug("PTR: %s" % name)
291 answers.append(name.replace("._pdl-datastream._tcp.local.", ""))
293 elif info[0] == QTYPE_TXT:
294 offset, name = read_data(offset, data, info[3])
297 while off < len(name):
300 result = name[off:off+l]
303 key, value = result.split('=')
310 log.debug("TXT: %s" % repr(txt))
312 y['device1'] = "MFG:Hewlett-Packard;MDL:%s;CLS:PRINTER;" % txt['ty']
314 log.debug("NO ty Key in txt: %s" % repr(txt))
317 y['note'] = txt['note']
319 elif info[0] == QTYPE_SRV:
320 offset, (priority, weight, port) = read_data_unpack(offset, data, "!HHH")
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.', '')
326 elif info[0] == QTYPE_AAAA: # ipv6 address
327 offset, result = read_data(offset, data, 16)
328 log.debug("AAAA: %s" % repr(result))
331 log.error("Unknown DNS record type (%d)." % info[0])
334 found_devices[y['ip']] = y
336 log.debug("Found %d devices" % len(found_devices))