Merge lp:~ptman/hipl/cleanup into lp:hipl

Proposed by Paul Tötterman
Status: Merged
Merged at revision: 6434
Proposed branch: lp:~ptman/hipl/cleanup
Merge into: lp:hipl
Diff against target: 1205 lines (+547/-282)
6 files modified
debian/hipl-dnsproxy.init (+1/-1)
tools/hipdnsproxy/dnsproxy.py (+319/-264)
tools/hipdnsproxy/dnsproxy_test.py (+182/-0)
tools/hipdnsproxy/hipdnsproxy.in (+11/-5)
tools/hipdnsproxy/resolvconf.py (+23/-7)
tools/hipdnsproxy/util.py (+11/-5)
To merge this branch: bzr merge lp:~ptman/hipl/cleanup
Reviewer Review Type Date Requested Status
Miika Komu Approve
Review via email: mp+190185@code.launchpad.net

Description of the change

This branch consists of various polishing and cleanup commits.

- Fix (retry and backoff) for a race condition
- Fix logging to syslog
- Fix upstream DNS server change when moving from one network to another
- Source format improvement as reported by pylint and pep8
- Split huge mainloop into smaller, testable pieces and added tests

To post a comment you must log in.
lp:~ptman/hipl/cleanup updated
6441. By Paul Tötterman

Fix several crashes when hipconf daemon cannot reach hipd

Revision history for this message
Miika Komu (miika-iki) wrote :

Nice work! I have been testing this and it seems to work fine especially after the last fix in 6441.

review: Approve
lp:~ptman/hipl/cleanup updated
6442. By Paul Tötterman

Merged trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/hipl-dnsproxy.init'
2--- debian/hipl-dnsproxy.init 2013-03-06 14:07:36 +0000
3+++ debian/hipl-dnsproxy.init 2013-10-11 15:24:31 +0000
4@@ -9,7 +9,7 @@
5 # Short-Description: HIP DNS proxy
6 ### END INIT INFO
7
8-DNSPROXY_OPTS='-k'
9+DNSPROXY_OPTS='-kS'
10 PID_FILE=/var/run/hipdnsproxy.pid
11 EXEC=/usr/sbin/hipdnsproxy
12
13
14=== modified file 'tools/hipdnsproxy/dnsproxy.py'
15--- tools/hipdnsproxy/dnsproxy.py 2013-07-11 15:08:01 +0000
16+++ tools/hipdnsproxy/dnsproxy.py 2013-10-11 15:24:31 +0000
17@@ -67,6 +67,7 @@
18 import logging
19 import logging.handlers
20 import os
21+import pprint
22 import re
23 import select
24 import signal
25@@ -79,10 +80,10 @@
26
27 # prepending (instead of appending) to make sure hosts.py does not
28 # collide with the system default
29+import DNS
30 import hosts
31 import resolvconf
32 import util
33-from DNS import Serialize, DeSerialize, Type
34
35
36 DEFAULT_HOSTS = '/etc/hosts'
37@@ -94,6 +95,7 @@
38 sys.stderr.write('Usage: %s\n' % os.path.split(sys.argv[0])[1])
39 if msg:
40 sys.stderr.write('Error: %r\n' % msg)
41+
42 sys.exit(1)
43
44
45@@ -103,44 +105,58 @@
46 def add_hit_ip_map(hit, addr):
47 """Add IP for HIT."""
48 logging.info('Associating HIT %s with IP %s', hit, addr)
49- subprocess.check_call(['hipconf', 'daemon', 'add', 'map', hit, addr],
50- stdout=open(os.devnull, 'w'),
51- stderr=subprocess.STDOUT)
52+ try:
53+ subprocess.check_call(['hipconf', 'daemon', 'add', 'map', hit, addr],
54+ stdout=open(os.devnull, 'w'),
55+ stderr=subprocess.STDOUT)
56+ except subprocess.CalledProcessError:
57+ logging.error('Got error from `hipconf daemon ...`. Is hipd up?')
58
59
60 def hit_to_lsi(hit):
61 """Return LSI for HIT if found."""
62- output = subprocess.Popen(['hipconf', 'daemon', 'hit-to-lsi', hit],
63- stdout=subprocess.PIPE,
64- stderr=subprocess.STDOUT).stdout
65-
66- for line in output:
67- match = LSI_RE.search(line)
68- if match:
69- return match.group('lsi')
70+ proc = subprocess.Popen(['hipconf', 'daemon', 'hit-to-lsi', hit],
71+ stdout=subprocess.PIPE,
72+ stderr=subprocess.STDOUT)
73+
74+ if proc.returncode == 254:
75+ logging.error('Cannot contact hipd. Is it running?')
76+ return
77+
78+ output = proc.stdout
79+
80+ try:
81+ for line in output:
82+ match = LSI_RE.search(line)
83+ if match:
84+ return match.group('lsi')
85+ except IOError:
86+ logging.error('Cannot read from `hipconf daemon ...`. Is hipd up?')
87
88
89 def lsi_to_hit(lsi):
90 """Return HIT for LSI if found."""
91- output = subprocess.Popen(['hipconf', 'daemon', 'lsi-to-hit', lsi],
92- stdout=subprocess.PIPE,
93- stderr=subprocess.STDOUT).stdout
94-
95- for line in output:
96- match = hosts.HIT_RE.search(line)
97- if match:
98- return match.group('hit')
99+ proc = subprocess.Popen(['hipconf', 'daemon', 'lsi-to-hit', lsi],
100+ stdout=subprocess.PIPE,
101+ stderr=subprocess.STDOUT)
102+
103+ if proc.returncode == 254:
104+ logging.error('Cannot contact hipd. Is it running?')
105+ return
106+
107+ output = proc.stdout
108+
109+ try:
110+ for line in output:
111+ match = hosts.HIT_RE.search(line)
112+ if match:
113+ return match.group('hit')
114+ except IOError:
115+ logging.error('Cannot read from `hipconf daemon ...`. Is hipd up?')
116
117
118 def is_reverse_hit_query(name):
119- """Check if the query is a reverse query to a HIT.
120-
121- >>> is_reverse_hit_query('::1')
122- False
123- >>> is_reverse_hit_query('8.e.b.8.b.3.c.9.1.a.0.c.e.e.2.c.c.e.d.0.9.c.'
124- ... '9.a.e.1.0.0.1.0.0.2.hit-to-ip.infrahip.net')
125- True
126- """
127+ """Check if the query is a reverse query to a HIT."""
128 if (name.endswith('.1.0.0.1.0.0.2.hit-to-ip.infrahip.net') and
129 len(name) == 86):
130 return True
131@@ -156,6 +172,7 @@
132 pidfile=None, prefix=None, server_ip=None, server_port=None):
133 self.bind_ip = bind_ip
134 self.bind_port = bind_port
135+ self.connected = False
136 self.disable_lsi = disable_lsi
137 self.dns_timeout = dns_timeout
138 self.fork = fork
139@@ -171,10 +188,13 @@
140 if self.hostsnames is None:
141 self.hostsnames = []
142
143+ self.clisock = None
144+ self.servsock = None
145 self.app_timeout = 1
146 self.hosts_ttl = 122
147 self.sent_queue = []
148 self.hosts = None
149+ self.query_id = 1
150 # Keyed by ('server_ip',server_port,query_id) tuple
151 self.sent_queue_d = {}
152 # required for ifconfig and hipconf in Fedora
153@@ -217,20 +237,26 @@
154 env = os.environ
155 if self.server_ip is None:
156 self.server_ip = env.get('SERVER', None)
157+
158 if self.server_port is None:
159 server_port = env.get('SERVERPORT', None)
160 if server_port is not None:
161 self.server_port = int(server_port)
162+
163 if self.server_port is None:
164 self.server_port = 53
165+
166 if self.bind_ip is None:
167 self.bind_ip = env.get('IP', None)
168+
169 if self.bind_ip is None:
170 self.bind_ip = '127.0.0.53'
171+
172 if self.bind_port is None:
173 bind_port = env.get('PORT', None)
174 if bind_port is not None:
175 self.bind_port = int(bind_port)
176+
177 if self.bind_port is None:
178 self.bind_port = 53
179
180@@ -238,7 +264,6 @@
181 """Recheck all hosts files."""
182 for hostsdb in self.hosts:
183 hostsdb.recheck()
184- return
185
186 def getaddr(self, ahn):
187 """Get a hostname matching address."""
188@@ -273,7 +298,6 @@
189 if result:
190 return result
191
192-
193 def killold(self):
194 """Kill process with PID from pidfile."""
195 try:
196@@ -284,6 +308,7 @@
197 else:
198 logging.error('Error opening pid file: %s', ioe)
199 sys.exit(1)
200+
201 try:
202 os.kill(int(ifile.readline().rstrip()), signal.SIGTERM)
203 except OSError, ose:
204@@ -293,6 +318,7 @@
205 else:
206 logging.error('Error terminating old process: %s', ose)
207 sys.exit(1)
208+
209 time.sleep(3)
210 ifile.close()
211
212@@ -306,6 +332,7 @@
213 else:
214 logging.error('Error opening pid file: %s', ioe)
215 sys.exit(1)
216+
217 ifile.readline()
218 global MYID
219 MYID = ifile.readline().rstrip()
220@@ -319,6 +346,7 @@
221 except IOError, ioe:
222 logging.error('Error opening pid file for writing: %s', ioe)
223 sys.exit(1)
224+
225 global MYID
226 MYID = '%d-%d' % (time.time(), os.getpid())
227 ofile.write('%d\n' % (os.getpid(),))
228@@ -338,19 +366,22 @@
229 proc = subprocess.Popen(['ifconfig', 'dummy0'],
230 stdout=subprocess.PIPE,
231 stderr=subprocess.STDOUT).stdout
232- result = proc.readline()
233- while result:
234- start = result.find('2001:1')
235- end = result.find('/28')
236+ line = proc.readline()
237+ while line:
238+ start = line.find('2001:1')
239+ end = line.find('/28')
240 if start != -1 and end != -1:
241- hit = result[start:end]
242+ hit = line[start:end]
243 if not self.getaddr(hit):
244 localhit.append(hit)
245- result = proc.readline()
246+
247+ line = proc.readline()
248+
249 proc.close()
250 ofile = open(self.hiphosts, 'a')
251 for i in range(len(localhit)):
252 ofile.write('%s\tlocalhit%s\n' % (localhit[i], i + 1))
253+
254 ofile.close()
255
256 def hip_cache_lookup(self, packet):
257@@ -364,12 +395,13 @@
258
259 # convert 1.2....1.0.0.1.0.0.2.ip6.arpa to a HIT and
260 # map host name to address from cache
261- if qtype == Type.PTR:
262+ if qtype == DNS.Type.PTR:
263 lr_ptr = None
264 addr_str = hosts.ptr_to_addr(qname)
265 if (not self.disable_lsi and addr_str is not None and
266 hosts.valid_lsi(addr_str)):
267 addr_str = lsi_to_hit(addr_str)
268+
269 lr_ptr = self.getaddr(addr_str)
270 lr_aaaa_hit = None
271 else:
272@@ -382,26 +414,29 @@
273 packet['questions'][0][0].startswith(self.prefix))):
274 if lr_a is not None:
275 add_hit_ip_map(lr_aaaa_hit[0], lr_a[0])
276+
277 if lr_aaaa is not None:
278 add_hit_ip_map(lr_aaaa_hit[0], lr_aaaa[0])
279- if qtype == Type.AAAA:
280+
281+ if qtype == DNS.Type.AAAA:
282 result = lr_aaaa_hit
283- elif qtype == Type.A and not self.disable_lsi:
284+ elif qtype == DNS.Type.A and not self.disable_lsi:
285 lsi = hit_to_lsi(lr_aaaa_hit[0])
286 if lsi is not None:
287 result = (lsi, lr_aaaa_hit[1])
288+
289 elif self.prefix and packet['questions'][0][0].startswith(self.prefix):
290 result = None
291- elif qtype == Type.AAAA:
292+ elif qtype == DNS.Type.AAAA:
293 result = lr_aaaa
294- elif qtype == Type.A:
295+ elif qtype == DNS.Type.A:
296 result = lr_a
297- elif qtype == Type.PTR and lr_ptr is not None:
298+ elif qtype == DNS.Type.PTR and lr_ptr is not None:
299 result = (lr_ptr, self.hosts_ttl)
300
301 if result is not None:
302 packet['answers'].append([packet['questions'][0][0], qtype, 1,
303- result[1], result[0]])
304+ result[1], result[0]])
305 packet['ancount'] = len(packet['answers'])
306 packet['qr'] = 1
307 return True
308@@ -415,7 +450,7 @@
309
310 dns_hit_found = False
311 for answer in packet['answers']:
312- if answer[1] == Type.HIP:
313+ if answer[1] == DNS.Type.HIP:
314 dns_hit_found = True
315 break
316
317@@ -426,20 +461,20 @@
318 lsi_ans = []
319
320 for answer in packet['answers']:
321- if answer[1] != Type.HIP:
322+ if answer[1] != DNS.Type.HIP:
323 continue
324
325 hit = socket.inet_ntop(socket.AF_INET6, answer[7])
326- hit_ans.append([qname, Type.AAAA, 1, answer[3], hit])
327+ hit_ans.append([qname, DNS.Type.AAAA, 1, answer[3], hit])
328
329- if qtype == Type.A and not self.disable_lsi:
330+ if qtype == DNS.Type.A and not self.disable_lsi:
331 lsi = hit_to_lsi(hit)
332 if lsi is not None:
333 lsi_ans.append([qname, 1, 1, self.hosts_ttl, lsi])
334
335 self.cache_name(qname, hit, answer[3])
336
337- if qtype == Type.AAAA and hit_found:
338+ if qtype == DNS.Type.AAAA and hit_found:
339 packet['answers'] = hit_ans
340 elif lsi is not None:
341 packet['answers'] = lsi_ans
342@@ -448,10 +483,211 @@
343
344 packet['ancount'] = len(packet['answers'])
345
346+ def handle_query(self, packet, sender):
347+ """Handle DNS query from downstream client."""
348+ qtype = packet['questions'][0][1]
349+
350+ sent_answer = False
351+
352+ if qtype in (DNS.Type.A, DNS.Type.AAAA, DNS.Type.PTR):
353+ if self.hip_cache_lookup(packet):
354+ try:
355+ outbuf = DNS.Serialize(packet).get_packet()
356+ self.servsock.sendto(outbuf, sender)
357+ sent_answer = True
358+ except socket.error:
359+ logging.exception('Exception:')
360+
361+ elif (self.prefix and
362+ packet['questions'][0][0].startswith(
363+ self.prefix)):
364+ # Query with HIP prefix for unsupported RR type.
365+ # Send empty response.
366+ packet['qr'] = 1
367+ try:
368+ outbuf = DNS.Serialize(packet).get_packet()
369+ self.servsock.sendto(outbuf, sender)
370+ sent_answer = True
371+ except socket.error:
372+ logging.exception('Exception:')
373+
374+ if self.connected and not sent_answer:
375+ logging.info('Query type %d for %s from %s',
376+ qtype, packet['questions'][0][0],
377+ (self.server_ip, self.server_port))
378+
379+ query = (packet, sender[0], sender[1], qtype)
380+ # FIXME: Should randomize for security
381+ self.query_id = (self.query_id % 65535) + 1
382+ pckt = copy.copy(packet)
383+ pckt['id'] = self.query_id
384+ if ((qtype == DNS.Type.AAAA or
385+ (qtype == DNS.Type.A and
386+ not self.disable_lsi)) and
387+ not is_reverse_hit_query(
388+ packet['questions'][0][0])):
389+
390+ if not self.prefix:
391+ pckt['questions'][0][1] = DNS.Type.HIP
392+
393+ if (self.prefix and
394+ pckt['questions'][0][0].startswith(
395+ self.prefix)):
396+ pckt['questions'][0][0] = pckt[
397+ 'questions'][0][0][len(self.prefix):]
398+ pckt['questions'][0][1] = DNS.Type.HIP
399+
400+ if qtype == DNS.Type.PTR and not self.disable_lsi:
401+ qname = packet['questions'][0][0]
402+ addr_str = hosts.ptr_to_addr(qname)
403+ if (addr_str is not None and
404+ hosts.valid_lsi(addr_str)):
405+ query = (packet, sender[0], sender[1],
406+ qname)
407+ hit_str = lsi_to_hit(addr_str)
408+ if hit_str is not None:
409+ pckt['questions'][0][0] = hosts.addr_to_ptr(hit_str)
410+
411+ outbuf = DNS.Serialize(pckt).get_packet()
412+ self.clisock.sendto(outbuf, (self.server_ip,
413+ self.server_port))
414+
415+ self.add_query(self.server_ip, self.server_port,
416+ self.query_id, query)
417+
418+ def handle_response(self, packet, sender):
419+ """Handle DNS response from upstream server."""
420+ if packet['qdcount'] == 0:
421+ logging.warn('Bad response from upstream server: %s',
422+ pprint.pformat(packet))
423+ return
424+
425+ # Find original query
426+ query_id_o = packet['id']
427+ query_o = self.find_query(sender[0], sender[1],
428+ query_id_o)
429+ if query_o and packet['qdcount'] > 0:
430+ qname = packet['questions'][0][0]
431+ qtype = packet['questions'][0][1]
432+ send_reply = True
433+ query_again = False
434+ hit_found = False
435+ packet_o = query_o[0]
436+ # Replace with the original query id
437+ packet['id'] = packet_o['id']
438+
439+ if qtype == DNS.Type.HIP and query_o[3] in (DNS.Type.A,
440+ DNS.Type.AAAA):
441+ # Restore qtype
442+ packet['questions'][0][1] = query_o[3]
443+ self.hip_lookup(packet)
444+ if packet['ancount'] > 0:
445+ hit_found = True
446+
447+ if (not self.prefix or
448+ (hit_found and not (self.getaaaa(qname) or
449+ self.geta(qname)))):
450+ query_again = True
451+ send_reply = False
452+ elif self.prefix:
453+ hit_found = True
454+ packet['questions'][0][0] = (
455+ self.prefix + packet['questions'][0][0])
456+ for answer in packet['answers']:
457+ answer[0] = self.prefix + answer[0]
458+
459+ elif qtype in (DNS.Type.A, DNS.Type.AAAA):
460+ hit = self.getaaaa_hit(qname)
461+ ip6 = self.getaaaa(qname)
462+ ip4 = self.geta(qname)
463+ for answer in packet['answers']:
464+ if answer[1] in (DNS.Type.A, DNS.Type.AAAA):
465+ self.cache_name(qname, answer[4],
466+ answer[3])
467+
468+ if hit is not None:
469+ for answer in packet['answers']:
470+ if (answer[1] == DNS.Type.A or
471+ (answer[1] == DNS.Type.AAAA and not
472+ hosts.valid_hit(answer[4]))):
473+ add_hit_ip_map(hit[0], answer[4])
474+
475+ # Reply with HIT/LSI once it's been mapped
476+ # to an IP
477+ if ip6 is None and ip4 is None:
478+ if (packet_o['ancount'] == 0 and
479+ not self.prefix):
480+ # No LSI available. Return IPv4
481+ tmp = packet['answers']
482+ packet = packet_o
483+ packet['answers'] = tmp
484+ packet['ancount'] = len(
485+ packet['answers'])
486+ else:
487+ packet = packet_o
488+ if self.prefix:
489+ packet['questions'][0][0] = (
490+ self.prefix + packet['questions'][0][0])
491+ for answer in packet['answers']:
492+ answer[0] = (self.prefix +
493+ answer[0])
494+
495+ else:
496+ send_reply = False
497+
498+ elif query_o[3] == 0:
499+ # Prefix is in use
500+ # IP was queried for cache only
501+ send_reply = False
502+
503+ elif qtype == DNS.Type.PTR and isinstance(query_o[3],
504+ str):
505+ packet['questions'][0][0] = query_o[3]
506+ for answer in packet['answers']:
507+ answer[0] = query_o[3]
508+
509+ if query_again:
510+ if hit_found:
511+ qtypes = [DNS.Type.AAAA, DNS.Type.A]
512+ pckt = copy.deepcopy(packet)
513+ else:
514+ qtypes = [query_o[3]]
515+ pckt = copy.copy(packet)
516+
517+ pckt['qr'] = 0
518+ pckt['answers'] = []
519+ pckt['ancount'] = 0
520+ pckt['nslist'] = []
521+ pckt['nscount'] = 0
522+ pckt['additional'] = []
523+ pckt['arcount'] = 0
524+ for qtype in qtypes:
525+ if self.prefix:
526+ query = (packet, query_o[1], query_o[2],
527+ 0)
528+ else:
529+ query = (packet, query_o[1], query_o[2],
530+ qtype)
531+
532+ self.query_id = (self.query_id % 65535) + 1
533+ pckt['id'] = self.query_id
534+ pckt['questions'][0][1] = qtype
535+ outbuf = DNS.Serialize(pckt).get_packet()
536+ self.clisock.sendto(outbuf, (self.server_ip,
537+ self.server_port))
538+ self.add_query(self.server_ip,
539+ self.server_port,
540+ self.query_id, query)
541+
542+ packet['questions'][0][1] = query_o[3]
543+
544+ if send_reply:
545+ outbuf = DNS.Serialize(packet).get_packet()
546+ self.servsock.sendto(outbuf, (query_o[1],
547+ query_o[2]))
548+
549 def mainloop(self, unused_args):
550 """HIP DNS proxy main loop."""
551- connected = False
552-
553 logging.info('Dns proxy for HIP started')
554
555 self.parameter_defaults()
556@@ -460,16 +696,15 @@
557 # avoid problems with other dns forwarders (e.g. dnsmasq)
558 os.system('ifconfig lo:53 %s' % (self.bind_ip,))
559
560- servsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
561+ self.servsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
562 try:
563- servsock.bind((self.bind_ip, self.bind_port))
564+ self.servsock.bind((self.bind_ip, self.bind_port))
565 except socket.error:
566 logging.error('Port %d already in use. See HOWTO.',
567 self.bind_port)
568 return
569
570-
571- servsock.settimeout(self.app_timeout)
572+ self.servsock.settimeout(self.app_timeout)
573
574 self.hosts = []
575 if self.hostsnames:
576@@ -484,8 +719,6 @@
577
578 self.write_local_hits_to_hosts()
579
580- query_id = 1
581-
582 util.init_wantdown() # Initialize signal handler for shutdown
583 util.init_hup() # Initialize signal handler for reload
584
585@@ -496,232 +729,54 @@
586 self.server_ip = self.resolvconf.nameserver
587
588 if util.wanthup():
589- logging.info('Received SIGHUP. Picking %s as new upstream '
590- 'DNS server', self.server_ip)
591+ logging.info('Received SIGHUP. Picking new upstream server')
592+ self.server_ip = self.resolvconf.nameserver
593 util.wanthup(False)
594
595- logging.info('Connecting to upstream DNS server...')
596+ logging.info('Connecting to upstream DNS server %s ...',
597+ self.server_ip)
598 if ':' not in self.server_ip:
599 server_family = socket.AF_INET
600 else:
601 server_family = socket.AF_INET6
602- clisock = socket.socket(server_family, socket.SOCK_DGRAM)
603- clisock.settimeout(self.dns_timeout)
604+
605+ self.clisock = socket.socket(server_family, socket.SOCK_DGRAM)
606+ self.clisock.settimeout(self.dns_timeout)
607 try:
608- clisock.connect((self.server_ip, self.server_port))
609- connected = True
610+ self.clisock.connect((self.server_ip, self.server_port))
611+ self.connected = True
612 logging.debug('... connected!')
613 self.resolvconf.nameserver = self.bind_ip
614 except socket.error:
615 logging.error('Connecting to upstream DNS server failed!')
616 time.sleep(3)
617- connected = False
618+ self.connected = False
619
620- while connected and (not util.wantdown()) and (not util.wanthup()):
621+ while self.connected and (not util.wantdown()) and (
622+ not util.wanthup()):
623 try:
624 self.hosts_recheck()
625
626- if connected:
627- rlist, _, _ = select.select([servsock, clisock], [], [],
628- 5.0)
629+ if self.connected:
630+ rlist, _, _ = select.select([self.servsock,
631+ self.clisock],
632+ [], [], 5.0)
633 else:
634- rlist, _, _ = select.select([servsock], [], [], 5.0)
635+ rlist, _, _ = select.select([self.servsock],
636+ [], [], 5.0)
637+
638 self.clean_queries()
639- if servsock in rlist: # Incoming DNS request
640- inbuf, from_a = servsock.recvfrom(2048)
641-
642- packet = DeSerialize(inbuf).get_dict()
643- qtype = packet['questions'][0][1]
644-
645- sent_answer = False
646-
647- if qtype in (Type.A, Type.AAAA, Type.PTR):
648- if self.hip_cache_lookup(packet):
649- try:
650- outbuf = Serialize(packet).get_packet()
651- servsock.sendto(outbuf, from_a)
652- sent_answer = True
653- except socket.error:
654- logging.exception('Exception:')
655- elif (self.prefix and
656- packet['questions'][0][0].startswith(
657- self.prefix)):
658- # Query with HIP prefix for unsupported RR type.
659- # Send empty response.
660- packet['qr'] = 1
661- try:
662- outbuf = Serialize(packet).get_packet()
663- servsock.sendto(outbuf, from_a)
664- sent_answer = True
665- except socket.error:
666- logging.exception('Exception:')
667-
668- if connected and not sent_answer:
669- logging.info('Query type %d for %s from %s',
670- qtype, packet['questions'][0][0],
671- (self.server_ip, self.server_port))
672-
673- query = (packet, from_a[0], from_a[1], qtype)
674- # FIXME: Should randomize for security
675- query_id = (query_id % 65535) + 1
676- pckt = copy.copy(packet)
677- pckt['id'] = query_id
678- if ((qtype == Type.AAAA or
679- (qtype == Type.A and
680- not self.disable_lsi)) and
681- not is_reverse_hit_query(
682- packet['questions'][0][0])):
683-
684- if not self.prefix:
685- pckt['questions'][0][1] = Type.HIP
686- if (self.prefix and
687- pckt['questions'][0][0].startswith(
688- self.prefix)):
689- pckt['questions'][0][0] = pckt[
690- 'questions'][0][0][len(self.prefix):]
691- pckt['questions'][0][1] = Type.HIP
692-
693- if qtype == Type.PTR and not self.disable_lsi:
694- qname = packet['questions'][0][0]
695- addr_str = hosts.ptr_to_addr(qname)
696- if (addr_str is not None and
697- hosts.valid_lsi(addr_str)):
698- query = (packet, from_a[0], from_a[1],
699- qname)
700- hit_str = lsi_to_hit(addr_str)
701- if hit_str is not None:
702- pckt['questions'][0][0] = \
703- hosts.addr_to_ptr(hit_str)
704-
705- outbuf = Serialize(pckt).get_packet()
706- clisock.sendto(outbuf, (self.server_ip,
707- self.server_port))
708-
709- self.add_query(self.server_ip, self.server_port,
710- query_id, query)
711-
712- if connected and clisock in rlist: # Incoming DNS reply
713- inbuf, from_a = clisock.recvfrom(2048)
714+ if self.servsock in rlist:
715+ payload, sender = self.servsock.recvfrom(2048)
716+ packet = DNS.DeSerialize(payload).get_dict()
717+ self.handle_query(packet, sender)
718+
719+ if self.connected and self.clisock in rlist:
720+ payload, sender = self.clisock.recvfrom(2048)
721 logging.info('Packet from DNS server %d bytes from %s',
722- len(inbuf), from_a)
723- packet = DeSerialize(inbuf).get_dict()
724-
725- # Find original query
726- query_id_o = packet['id']
727- query_o = self.find_query(from_a[0], from_a[1],
728- query_id_o)
729- if query_o:
730- qname = packet['questions'][0][0]
731- qtype = packet['questions'][0][1]
732- send_reply = True
733- query_again = False
734- hit_found = False
735- packet_o = query_o[0]
736- # Replace with the original query id
737- packet['id'] = packet_o['id']
738-
739- if qtype == Type.HIP and query_o[3] in (Type.A,
740- Type.AAAA):
741- # Restore qtype
742- packet['questions'][0][1] = query_o[3]
743- self.hip_lookup(packet)
744- if packet['ancount'] > 0:
745- hit_found = True
746- if (not self.prefix or
747- (hit_found and not (self.getaaaa(qname) or
748- self.geta(qname)))):
749- query_again = True
750- send_reply = False
751- elif self.prefix:
752- hit_found = True
753- packet['questions'][0][0] = (
754- self.prefix + packet['questions'][0][0])
755- for answer in packet['answers']:
756- answer[0] = self.prefix + answer[0]
757-
758- elif qtype in (Type.A, Type.AAAA):
759- hit = self.getaaaa_hit(qname)
760- ip6 = self.getaaaa(qname)
761- ip4 = self.geta(qname)
762- for answer in packet['answers']:
763- if answer[1] in (Type.A, Type.AAAA):
764- self.cache_name(qname, answer[4],
765- answer[3])
766- if hit is not None:
767- for answer in packet['answers']:
768- if (answer[1] == Type.A or
769- (answer[1] == Type.AAAA and not
770- hosts.valid_hit(answer[4]))):
771- add_hit_ip_map(hit[0], answer[4])
772- # Reply with HIT/LSI once it's been mapped
773- # to an IP
774- if ip6 is None and ip4 is None:
775- if (packet_o['ancount'] == 0 and
776- not self.prefix):
777- # No LSI available. Return IPv4
778- tmp = packet['answers']
779- packet = packet_o
780- packet['answers'] = tmp
781- packet['ancount'] = len(
782- packet['answers'])
783- else:
784- packet = packet_o
785- if self.prefix:
786- packet['questions'][0][0] = \
787- (self.prefix +
788- packet['questions'][0][0])
789- for answer in packet['answers']:
790- answer[0] = (self.prefix +
791- answer[0])
792- else:
793- send_reply = False
794- elif query_o[3] == 0:
795- # Prefix is in use
796- # IP was queried for cache only
797- send_reply = False
798-
799- elif qtype == Type.PTR and isinstance(query_o[3],
800- str):
801- packet['questions'][0][0] = query_o[3]
802- for answer in packet['answers']:
803- answer[0] = query_o[3]
804-
805- if query_again:
806- if hit_found:
807- qtypes = [Type.AAAA, Type.A]
808- pckt = copy.deepcopy(packet)
809- else:
810- qtypes = [query_o[3]]
811- pckt = copy.copy(packet)
812- pckt['qr'] = 0
813- pckt['answers'] = []
814- pckt['ancount'] = 0
815- pckt['nslist'] = []
816- pckt['nscount'] = 0
817- pckt['additional'] = []
818- pckt['arcount'] = 0
819- for qtype in qtypes:
820- if self.prefix:
821- query = (packet, query_o[1], query_o[2],
822- 0)
823- else:
824- query = (packet, query_o[1], query_o[2],
825- qtype)
826- query_id = (query_id % 65535) + 1
827- pckt['id'] = query_id
828- pckt['questions'][0][1] = qtype
829- outbuf = Serialize(pckt).get_packet()
830- clisock.sendto(outbuf, (self.server_ip,
831- self.server_port))
832- self.add_query(self.server_ip,
833- self.server_port,
834- query_id, query)
835- packet['questions'][0][1] = query_o[3]
836-
837- if send_reply:
838- outbuf = Serialize(packet).get_packet()
839- servsock.sendto(outbuf, (query_o[1],
840- query_o[2]))
841+ len(payload), sender)
842+ packet = DNS.DeSerialize(payload).get_dict()
843+ self.handle_response(packet, sender)
844 except (select.error, OSError), exc:
845 if exc[0] == errno.EINTR:
846 pass
847@@ -729,7 +784,7 @@
848 logging.exception('Exception:')
849 except socket.error, exc:
850 logging.info('Connection to upstream DNS server lost')
851- connected = False
852+ self.connected = False
853
854 logging.info('Wants down')
855 self.resolvconf.restore()
856
857=== added file 'tools/hipdnsproxy/dnsproxy_test.py'
858--- tools/hipdnsproxy/dnsproxy_test.py 1970-01-01 00:00:00 +0000
859+++ tools/hipdnsproxy/dnsproxy_test.py 2013-10-11 15:24:31 +0000
860@@ -0,0 +1,182 @@
861+#! /usr/bin/env python
862+
863+# Copyright (c) 2012 Aalto University and RWTH Aachen University.
864+#
865+# Permission is hereby granted, free of charge, to any person
866+# obtaining a copy of this software and associated documentation
867+# files (the "Software"), to deal in the Software without
868+# restriction, including without limitation the rights to use,
869+# copy, modify, merge, publish, distribute, sublicense, and/or sell
870+# copies of the Software, and to permit persons to whom the
871+# Software is furnished to do so, subject to the following
872+# conditions:
873+#
874+# The above copyright notice and this permission notice shall be
875+# included in all copies or substantial portions of the Software.
876+#
877+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
878+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
879+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
880+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
881+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
882+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
883+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
884+# OTHER DEALINGS IN THE SOFTWARE.
885+
886+# pylint: disable-msg=C0111,R0903,C0103,R0904
887+
888+import DNS
889+import dnsproxy
890+import logging
891+import subprocess
892+import StringIO
893+import unittest
894+
895+
896+# Template DNS packet for used as basis of other packets in testing (rfc2929)
897+TEMPLATE_PACKET = {'aa': 0, # authoritativy answer
898+ 'additional': [], # additional records
899+ 'ancount': 0, # answer count, len(answers)
900+ 'answers': [], # answer records
901+ 'arcount': 0, # additional count, len(additional)
902+ 'id': 0, # Transaction id
903+ 'nscount': 0, # ns count, len(nslist)
904+ 'nslist': [], # NS records
905+ 'opcode': 0, # 0 = query (rfc1035)
906+ # 1 = inverse query (rfc1035)
907+ # 2 = status (rfc1035)
908+ # 4 = notify (rfc1996)
909+ # 5 = update (rfc2136)
910+ 'qdcount': 0, # query count, len(questions)
911+ 'qr': 0, # 0 = query, 1 = response
912+ 'questions': [], # questions
913+ 'ra': 1, # recursion available flag
914+ 'rcode': 0, # 0 = no error (rfc1035)
915+ # 1 = format error (rfc1035)
916+ # 2 = server failure (rfc 1035)
917+ # 3 = nxdomain (rfc1035)
918+ # 4 = not implemented (rfc1035)
919+ # 5 = query refused (rfc1035)
920+ # 6 = yxdomain (rfc2136)
921+ # 7 = yxrrset (rfc2136)
922+ # 8 = nxrrset (rfc2136)
923+ # 9 = not authoritative (rfc2136)
924+ # 10 = not in zone (rfc2136)
925+ # 16 = bad tsig (rfc2845)
926+ # 17 = bad key (rfc2845)
927+ # 18 = bad time (rfc2845)
928+ # 19 = bad tkey mode (rfc2930)
929+ # 20 = bad key name (rfc2930)
930+ # 21 = bad algorithm (rfc2930)
931+ 'rd': 0, # recursion desired flag
932+ 'tc': 0, # message truncated flag
933+ 'z': 0} # no longer in use, ignore (rfc2929)
934+SIMPLE_RESPONSE = TEMPLATE_PACKET.copy()
935+SIMPLE_RESPONSE.update({'ancount': 1,
936+ 'answers': (('example.com', DNS.Type.A, DNS.Class.IN,
937+ 60, '127.0.0.1'),),
938+ 'qdcount': 1,
939+ 'questions': (('example.com', DNS.Type.A,
940+ DNS.Class.IN),),
941+ 'qr': 1})
942+
943+SIMPLE_QUERY = TEMPLATE_PACKET.copy()
944+SIMPLE_QUERY.update({'qdcount': 1,
945+ 'questions': (('example.com', DNS.Type.A,
946+ DNS.Class.IN),)})
947+
948+
949+HIT = '2001:1b:a9be:c6a6:34e5:8361:c07f:a990'
950+LSI = '1.0.0.1'
951+
952+
953+class MockSocket(object):
954+ def sendto(self, payload, sender):
955+ pass
956+
957+
958+class MockFile(object):
959+ def __init__(self, exc=None, payload=None):
960+ self.exc = exc
961+ self.payload = StringIO.StringIO(payload)
962+
963+ def __iter__(self):
964+ if self.exc is not None:
965+ return self
966+ return self.payload.__iter__()
967+
968+ def next(self):
969+ if self.exc is not None:
970+ raise self.exc
971+ raise StopIteration
972+
973+
974+class MockPopen(object):
975+ def __init__(self, exc=None, stdout=None, returncode=0):
976+ if exc is not None:
977+ self.stdout = MockFile(exc=exc, payload=stdout)
978+ else:
979+ self.stdout = StringIO.StringIO(stdout)
980+
981+ self.returncode = returncode
982+
983+
984+class DNSProxyTest(unittest.TestCase):
985+ def setUp(self):
986+ logging.basicConfig(level=logging.DEBUG)
987+ self.dnsproxy = dnsproxy.DNSProxy()
988+ self.dnsproxy.hosts = []
989+ self.dnsproxy.clisock = MockSocket()
990+ self.dnsproxy.servsock = MockSocket()
991+
992+ def test_handle_response(self):
993+ self.dnsproxy.add_query('127.0.0.1', 53, 0,
994+ (SIMPLE_QUERY, '127.0.0.1', 53,
995+ SIMPLE_QUERY['questions'][0][1]))
996+ self.dnsproxy.handle_response(SIMPLE_RESPONSE, ('127.0.0.1', 53))
997+
998+ def test_handle_query(self):
999+ self.dnsproxy.handle_query(SIMPLE_QUERY, ('127.0.0.1', 53))
1000+
1001+ def test_is_reverse_hit_query(self):
1002+ self.assertFalse(dnsproxy.is_reverse_hit_query('::1'))
1003+ name = ('8.e.b.8.b.3.c.9.1.a.0.c.e.e.2.c.c.e.d.0.9.c.9.a.e.1.0.0.1.0.'
1004+ '0.2.hit-to-ip.infrahip.net')
1005+ self.assertTrue(dnsproxy.is_reverse_hit_query(name))
1006+
1007+ def test_hit_to_lsi(self):
1008+ subprocess.Popen = lambda *x, **y: MockPopen(exc=IOError('hit_to_lsi'))
1009+ lsi = dnsproxy.hit_to_lsi(HIT)
1010+ self.assertIsNone(lsi)
1011+
1012+ subprocess.Popen = lambda *x, **y: MockPopen(returncode=254)
1013+ lsi = dnsproxy.hit_to_lsi(HIT)
1014+ self.assertIsNone(lsi)
1015+
1016+ subprocess.Popen = lambda *x, **y: MockPopen(stdout=LSI)
1017+ lsi = dnsproxy.hit_to_lsi(HIT)
1018+ self.assertEqual(lsi, LSI)
1019+
1020+ def test_lsi_to_hit(self):
1021+ subprocess.Popen = lambda *x, **y: MockPopen(exc=IOError('lsi_to_hit'))
1022+ hit = dnsproxy.lsi_to_hit(LSI)
1023+ self.assertIsNone(hit)
1024+
1025+ subprocess.Popen = lambda *x, **y: MockPopen(returncode=254)
1026+ hit = dnsproxy.lsi_to_hit(LSI)
1027+ self.assertIsNone(hit)
1028+
1029+ subprocess.Popen = lambda *x, **y: MockPopen(stdout=HIT)
1030+ hit = dnsproxy.lsi_to_hit(LSI)
1031+ self.assertEqual(hit, HIT)
1032+
1033+ def test_add_hit_ip_map(self):
1034+ # pylint: disable-msg=W0613
1035+ def check_call(*x, **y):
1036+ raise subprocess.CalledProcessError(254, 'hipconf daemon')
1037+ subprocess.check_call = check_call
1038+ self.assertIsNone(dnsproxy.add_hit_ip_map(HIT, LSI))
1039+
1040+
1041+if __name__ == '__main__':
1042+ unittest.main()
1043
1044=== modified file 'tools/hipdnsproxy/hipdnsproxy.in'
1045--- tools/hipdnsproxy/hipdnsproxy.in 2013-02-22 15:16:12 +0000
1046+++ tools/hipdnsproxy/hipdnsproxy.in 2013-10-11 15:24:31 +0000
1047@@ -39,6 +39,8 @@
1048 optparser.add_option('-p', '--serverport', dest='server_port',
1049 action='store', type='int', default=None,
1050 help='DNS server port')
1051+ optparser.add_option('-S', '--syslog', dest='syslog', action='store_true',
1052+ default=False, help='Log to syslog')
1053 optparser.add_option('-t', '--dns-timeout', dest='dns_timeout',
1054 action='store', type='float', default=2.0,
1055 help='DNS timeout')
1056@@ -58,15 +60,19 @@
1057
1058 (options, args) = optparser.parse_args()
1059
1060+ logging.getLogger().setLevel(options.loglevel)
1061+
1062 child = False
1063 if (options.background):
1064 child = util.daemonize()
1065 else:
1066- logging.getLogger().setLevel(options.loglevel)
1067- loghandler = logging.StreamHandler(sys.stderr)
1068- loghandler.setFormatter(logging.Formatter(
1069- '%(asctime)s %(levelname)-8s %(message)s'))
1070- logging.getLogger().addHandler(loghandler)
1071+ if options.syslog:
1072+ util.log2syslog()
1073+ else:
1074+ loghandler = logging.StreamHandler(sys.stderr)
1075+ loghandler.setFormatter(logging.Formatter(
1076+ '%(asctime)s %(levelname)-8s %(message)s'))
1077+ logging.getLogger().addHandler(loghandler)
1078
1079 dnsp = dnsproxy.DNSProxy(bind_ip=options.bind_ip,
1080 bind_port=options.bind_port,
1081
1082=== modified file 'tools/hipdnsproxy/resolvconf.py'
1083--- tools/hipdnsproxy/resolvconf.py 2013-08-11 15:03:36 +0000
1084+++ tools/hipdnsproxy/resolvconf.py 2013-10-11 15:24:31 +0000
1085@@ -31,6 +31,7 @@
1086 import os
1087 import subprocess
1088 import tempfile
1089+import time
1090
1091
1092 class FragmentFile(object):
1093@@ -40,8 +41,7 @@
1094 def __init__(self, fragments):
1095 self.fragments = fragments
1096 self.current = None
1097- if self.fragments:
1098- self.current = open(self.fragments.pop(0), 'rb')
1099+ self.nextfragment()
1100
1101 def __iter__(self):
1102 return self
1103@@ -52,6 +52,23 @@
1104 def __exit__(self, exc_type, exc_value, trackeback):
1105 pass
1106
1107+ def nextfragment(self):
1108+ """Advance current fragment. Returns True when successful."""
1109+ if self.fragments:
1110+ fragment = self.fragments.pop(0)
1111+ retries = 3
1112+ while retries > 0:
1113+ try:
1114+ self.current = open(fragment, 'rb')
1115+ return True
1116+ except IOError, ioe:
1117+ if ioe.errno != errno.ENOENT:
1118+ raise
1119+ time.sleep(0.1)
1120+ retries -= 1
1121+ self.current = None
1122+ return False
1123+
1124 def next(self):
1125 """Returns next line in virtual file."""
1126 if self.current is None:
1127@@ -60,8 +77,7 @@
1128 try:
1129 return self.current.next()
1130 except StopIteration, sie:
1131- if self.fragments:
1132- self.current = open(self.fragments.pop(0), 'rb')
1133+ if self.nextfragment():
1134 return self.next()
1135 else:
1136 raise sie
1137@@ -95,10 +111,10 @@
1138 self.config = None
1139
1140 if self.using_resolvconf and isinstance(ResolvConf.FRAGMENT_DIR, list):
1141+ # pylint: disable-msg=W0141
1142 ResolvConf.FRAGMENT_DIR = filter(os.path.exists,
1143 ResolvConf.FRAGMENT_DIR)[0]
1144
1145-
1146 def realpath(self):
1147 """Return the real path to the actual resolv.conf."""
1148 return os.path.realpath(self.path)
1149@@ -126,13 +142,13 @@
1150
1151 frags = [x.strip() for x in out]
1152
1153- return [os.path.join(ResolvConf.FRAGMENT_DIR, x) for x in frags]
1154+ return [os.path.join(ResolvConf.FRAGMENT_DIR, x)
1155+ for x in frags]
1156 except IOError, ioe:
1157 if ioe.errno != errno.EINTR:
1158 raise
1159 retries -= 1
1160
1161-
1162 @classmethod
1163 def resolvconf_add(cls, ifile):
1164 """Pass new fragment to resolvconf(8)."""
1165
1166=== modified file 'tools/hipdnsproxy/util.py'
1167--- tools/hipdnsproxy/util.py 2013-07-11 15:08:01 +0000
1168+++ tools/hipdnsproxy/util.py 2013-10-11 15:24:31 +0000
1169@@ -87,6 +87,7 @@
1170 """Check if SIGINT or SIGTERM has been received."""
1171 return __FLAGS['down']
1172
1173+
1174 def wanthup(reset=None):
1175 """Check if SIGHUP has been received."""
1176 previous = __FLAGS['hup']
1177@@ -173,6 +174,15 @@
1178 return '%dd%02dh%02dm%02ds' % (days, hours, mins, secs)
1179
1180
1181+def log2syslog():
1182+ """Configure logging to send messages to syslog."""
1183+ loghandler = logging.handlers.SysLogHandler(address='/dev/log',
1184+ facility=logging.handlers.SysLogHandler.LOG_DAEMON)
1185+ loghandler.setFormatter(logging.Formatter(
1186+ 'hipdnsproxy[%(process)s] %(levelname)-8s %(message)s'))
1187+ logging.getLogger().addHandler(loghandler)
1188+
1189+
1190 def daemonize():
1191 """Daemonize current process."""
1192 pid = os.fork()
1193@@ -193,11 +203,7 @@
1194 if pid:
1195 sys.exit(0)
1196
1197- loghandler = logging.handlers.SysLogHandler(address='/dev/log',
1198- facility=logging.handlers.SysLogHandler.LOG_DAEMON)
1199- loghandler.setFormatter(logging.Formatter(
1200- 'hipdnsproxy[%(process)s] %(levelname)-8s %(message)s'))
1201- logging.getLogger().addHandler(loghandler)
1202+ log2syslog()
1203
1204 sys.stdout.flush()
1205 sys.stderr.flush()

Subscribers

People subscribed via source and target branches