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
|
#!/usr/bin/python
#
# use avahi to find a _apt_proxy._tcp provider and return
# a http proxy string suitable for apt
import asyncore
import functools
import os
import socket
import sys
import time
from subprocess import Popen, PIPE, call
from syslog import syslog, LOG_INFO, LOG_USER
DEFAULT_CONNECT_TIMEOUT_SEC = 2
def DEBUG(msg):
if "--debug" in sys.argv:
sys.stderr.write(msg + "\n")
def get_avahi_discover_timeout():
APT_AVAHI_TIMEOUT_VAR = "APT::Avahi-Discover::Timeout"
p = Popen(
["/usr/bin/apt-config", "shell", "TIMEOUT", APT_AVAHI_TIMEOUT_VAR],
stdout=PIPE)
stdout, stderr = p.communicate()
if not stdout:
DEBUG(
"no timeout set, using default '%s'" % DEFAULT_CONNECT_TIMEOUT_SEC)
return DEFAULT_CONNECT_TIMEOUT_SEC
if not stdout.startswith("TIMEOUT="):
raise ValueError("got unexpected apt-config output: '%s'" % stdout)
varname, sep, value = stdout.strip().partition("=")
timeout = int(value.strip("'"))
DEBUG("using timeout: '%s'" % timeout)
return timeout
@functools.total_ordering
class AptAvahiClient(asyncore.dispatcher):
def __init__(self, addr):
asyncore.dispatcher.__init__(self)
if is_ipv6(addr[0]):
self.create_socket(socket.AF_INET6, socket.SOCK_STREAM)
self.connect( (addr[0], addr[1], 0, 0) )
else:
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect(addr)
self._time_init = time.time()
self.time_to_connect = sys.maxint
self.address = addr
def handle_connect(self):
self.time_to_connect = time.time() - self._time_init
self.close()
def __eq__(self, other):
return self.time_to_connect == other.time_to_connect
def __lt__(self, other):
return self.time_to_connect < other.time_to_connect
def __repr__(self):
return "<%s> %s: %s" % (
self.__class__.__name__, self.addr, self.time_to_connect)
def log(self, message):
syslog((LOG_INFO|LOG_USER), '%s\n' % str(message))
def log_info(self, message, type='info'):
if type not in self.ignore_log_types:
self.log('%s: %s' % (type, message))
def is_ipv6(a):
return ':' in a
def is_linklocal(addr):
# Link-local should start with fe80 and six null bytes
return addr.startswith("fe80::")
def get_proxy_host_port_from_avahi():
service = '_apt_proxy._tcp'
# Obtain all of the services addresses from avahi, pulling the IPv6
# addresses to the top.
addr4 = []
addr6 = []
p = Popen(['avahi-browse', '-kprtf', service], stdout=PIPE)
DEBUG("avahi-browse output:")
for line in p.stdout:
DEBUG(" '%s'" % line)
if line.startswith('='):
tokens = line.split(';')
addr = tokens[7]
port = int(tokens[8])
if is_ipv6(addr):
# We need to skip ipv6 link-local addresses since
# APT can't use them
if not is_linklocal(addr):
addr6.append((addr, port))
else:
addr4.append((addr, port))
# Run through the offered addresses and see if we we have a bound local
# address for it.
addrs = []
for (ip, port) in addr6 + addr4:
try:
res = socket.getaddrinfo(ip, port, 0, 0, 0, socket.AI_ADDRCONFIG)
if res:
addrs.append((ip, port))
except socket.gaierror:
pass
if not addrs:
return None
# sort by answering speed
hosts = []
for addr in addrs:
hosts.append(AptAvahiClient(addr))
# 2s timeout, arbitray
timeout = get_avahi_discover_timeout()
asyncore.loop(timeout=timeout)
DEBUG("sorted hosts: '%s'" % sorted(hosts))
# No host wanted to connect
if (all(h.time_to_connect == sys.maxint for h in hosts)):
return None
fastest_host = sorted(hosts)[0]
fastest_address = fastest_host.address
return fastest_address
if __name__ == "__main__":
# Dump the approved address out in an appropriate format.
address = get_proxy_host_port_from_avahi()
if address:
(ip, port) = address
if is_ipv6(ip):
print "http://[%s]:%s/" % (ip, port)
else:
print "http://%s:%s/" % (ip, port)
|