3 # use avahi to find a _apt_proxy._tcp provider and return
4 # a http proxy string suitable for apt
12 from subprocess import Popen, PIPE, call
13 from syslog import syslog, LOG_INFO, LOG_USER
15 DEFAULT_CONNECT_TIMEOUT_SEC = 2
18 if "--debug" in sys.argv:
19 sys.stderr.write(msg + "\n")
22 def get_avahi_discover_timeout():
23 APT_AVAHI_TIMEOUT_VAR = "APT::Avahi-Discover::Timeout"
25 ["/usr/bin/apt-config", "shell", "TIMEOUT", APT_AVAHI_TIMEOUT_VAR],
27 stdout, stderr = p.communicate()
30 "no timeout set, using default '%s'" % DEFAULT_CONNECT_TIMEOUT_SEC)
31 return DEFAULT_CONNECT_TIMEOUT_SEC
32 if not stdout.startswith("TIMEOUT="):
33 raise ValueError("got unexpected apt-config output: '%s'" % stdout)
34 varname, sep, value = stdout.strip().partition("=")
35 timeout = int(value.strip("'"))
36 DEBUG("using timeout: '%s'" % timeout)
39 @functools.total_ordering
40 class AptAvahiClient(asyncore.dispatcher):
41 def __init__(self, addr):
42 asyncore.dispatcher.__init__(self)
44 self.create_socket(socket.AF_INET6, socket.SOCK_STREAM)
45 self.connect( (addr[0], addr[1], 0, 0) )
47 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
49 self._time_init = time.time()
50 self.time_to_connect = sys.maxint
52 def handle_connect(self):
53 self.time_to_connect = time.time() - self._time_init
55 def __eq__(self, other):
56 return self.time_to_connect == other.time_to_connect
57 def __lt__(self, other):
58 return self.time_to_connect < other.time_to_connect
60 return "<%s> %s: %s" % (
61 self.__class__.__name__, self.addr, self.time_to_connect)
62 def log(self, message):
63 syslog((LOG_INFO|LOG_USER), '%s\n' % str(message))
64 def log_info(self, message, type='info'):
65 if type not in self.ignore_log_types:
66 self.log('%s: %s' % (type, message))
72 def is_linklocal(addr):
73 # Link-local should start with fe80 and six null bytes
74 return addr.startswith("fe80::")
76 def get_proxy_host_port_from_avahi():
77 service = '_apt_proxy._tcp'
79 # Obtain all of the services addresses from avahi, pulling the IPv6
80 # addresses to the top.
83 p = Popen(['avahi-browse', '-kprtf', service], stdout=PIPE)
84 DEBUG("avahi-browse output:")
87 if line.startswith('='):
88 tokens = line.split(';')
92 # We need to skip ipv6 link-local addresses since
94 if not is_linklocal(addr):
95 addr6.append((addr, port))
97 addr4.append((addr, port))
99 # Run through the offered addresses and see if we we have a bound local
102 for (ip, port) in addr6 + addr4:
104 res = socket.getaddrinfo(ip, port, 0, 0, 0, socket.AI_ADDRCONFIG)
106 addrs.append((ip, port))
107 except socket.gaierror:
112 # sort by answering speed
115 hosts.append(AptAvahiClient(addr))
116 # 2s timeout, arbitray
117 timeout = get_avahi_discover_timeout()
118 asyncore.loop(timeout=timeout)
119 DEBUG("sorted hosts: '%s'" % sorted(hosts))
121 # No host wanted to connect
122 if (all(h.time_to_connect == sys.maxint for h in hosts)):
125 fastest_host = sorted(hosts)[0]
126 fastest_address = fastest_host.address
127 return fastest_address
130 if __name__ == "__main__":
131 # Dump the approved address out in an appropriate format.
132 address = get_proxy_host_port_from_avahi()
136 print "http://[%s]:%s/" % (ip, port)
138 print "http://%s:%s/" % (ip, port)