-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathmdns.py
155 lines (121 loc) · 3.84 KB
/
mdns.py
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import logging
import socket
import struct
import threading
import traceback
from dnslib.dns import *
import devspec
log = logging.getLogger()
MDNS_IP = '224.0.0.251'
MDNS_PORT = 5353
services = []
def add_service(svc):
services.append(svc + '.local.')
def mreqn(maddr):
return struct.pack("4sii", socket.inet_aton(maddr), socket.INADDR_ANY, 0)
class MDNS:
def __init__(self):
self.lock = threading.Lock()
self.found = set()
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind(('', MDNS_PORT))
self.mcast = False
def close(self):
if self.mcast:
self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_DROP_MEMBERSHIP,
mreqn(MDNS_IP))
self.socket.close()
def recv(self):
return self.socket.recv(65536)
def send(self, buf):
return self.socket.sendto(buf, (MDNS_IP, MDNS_PORT))
def req(self):
if not services:
return
if not self.mcast:
try:
self.socket.setsockopt(socket.IPPROTO_IP,
socket.IP_ADD_MEMBERSHIP,
mreqn(MDNS_IP))
self.mcast = True
except:
return
try:
q = DNSRecord()
for svc in services:
q.add_question(DNSQuestion(svc, QTYPE.PTR))
self.send(q.pack())
except Exception as e:
log.error('Error sending MDNS request: %s', e)
def get_devices(self):
with self.lock:
ret = self.found.copy()
self.found.clear()
return ret
def parse_record(self, rec):
ptr = set()
srv = {}
ips = {}
for rr in rec.auth + rec.rr + rec.ar:
rname = str(rr.rname)
if rr.rtype == QTYPE.PTR:
if rname in services:
ptr.add(str(rr.rdata.label))
if rr.rtype == QTYPE.SRV:
if len(rr.rname.label) < 3:
continue
proto = str(rr.rname.label[-2], encoding='ascii').lstrip('_')
if proto not in ['tcp', 'udp']:
continue
srv[rname] = devspec.create(
method=proto,
target=str(rr.rdata.target),
port=rr.rdata.port
)
if rr.rtype == QTYPE.A:
ips[rname] = rr.rdata
for k, v in srv.items():
t = v.target
if t in ips:
srv[k] = v._replace(target=str(ips[t]))
with self.lock:
for p in ptr & srv.keys():
self.found.add(srv[p])
def run(self):
while True:
try:
pkt = self.recv()
rec = DNSRecord.parse(pkt)
log.debug('--- BEGIN RECORD ---')
log.debug(rec)
log.debug('--- END RECORD ---')
self.parse_record(rec)
except DNSError:
continue
except:
log.error('Exception parsing record')
traceback.print_exc()
def start(self):
t = threading.Thread(target=self.run)
t.daemon = True
t.start()
if __name__ == '__main__':
import sys
import time
argv = sys.argv[1:]
level = logging.INFO
if argv and argv[0] == '-d':
level = logging.DEBUG
argv.pop(0)
logging.basicConfig(format='%(message)s', level=level)
for s in argv:
add_service(s)
mdns = MDNS()
mdns.start()
mdns.req()
while True:
devices = mdns.get_devices()
for d in devices:
log.info('%s %d', d.target, d.port)
time.sleep(1)