1
# Copyright (c) 2001-2006 Twisted Matrix Laboratories.
2
# See LICENSE for details.
6
from zope.interface import implements
8
from twisted.internet import error, interfaces
10
from twisted.names import client
12
class _SRVConnector_ClientFactoryWrapper:
13
def __init__(self, connector, wrappedFactory):
14
self.__connector = connector
15
self.__wrappedFactory = wrappedFactory
17
def startedConnecting(self, connector):
18
self.__wrappedFactory.startedConnecting(self.__connector)
20
def clientConnectionFailed(self, connector, reason):
21
self.__connector.connectionFailed(reason)
23
def clientConnectionLost(self, connector, reason):
24
self.__connector.connectionLost(reason)
26
def __getattr__(self, key):
27
return getattr(self.__wrappedFactory, key)
30
"""A connector that looks up DNS SRV records. See RFC2782."""
32
implements(interfaces.IConnector)
36
def __init__(self, reactor, service, domain, factory,
37
protocol='tcp', connectFuncName='connectTCP',
41
self.reactor = reactor
42
self.service = service
44
self.factory = factory
46
self.protocol = protocol
47
self.connectFuncName = connectFuncName
48
self.connectFuncArgs = connectFuncArgs
49
self.connectFuncKwArgs = connectFuncKwArgs
53
self.orderedServers = None # list of servers already used in this round
56
"""Start connection to remote server."""
57
self.factory.doStart()
58
self.factory.startedConnecting(self)
61
if self.domain is None:
62
self.connectionFailed(error.DNSLookupError("Domain is not defined."))
64
d = client.lookupService('_%s._%s.%s' % (self.service,
67
d.addCallback(self._cbGotServers)
68
d.addCallback(lambda x, self=self: self._reallyConnect())
69
d.addErrback(self.connectionFailed)
70
elif self.connector is None:
73
self.connector.connect()
75
def _cbGotServers(self, (answers, auth, add)):
76
if len(answers)==1 and answers[0].payload.target=='.':
77
# decidedly not available
78
raise error.DNSLookupError("Service %s not available for domain %s."
79
% (repr(self.service), repr(self.domain)))
82
self.orderedServers = []
84
self.orderedServers.append((a.payload.priority, a.payload.weight,
85
str(a.payload.target), a.payload.port))
87
def _serverCmp(self, a, b):
89
return cmp(a[0], b[0])
91
return cmp(a[1], b[1])
94
assert self.servers is not None
95
assert self.orderedServers is not None
97
if not self.servers and not self.orderedServers:
98
# no SRV record, fall back..
99
return self.domain, self.service
101
if not self.servers and self.orderedServers:
103
self.servers = self.orderedServers
104
self.orderedServers = []
108
self.servers.sort(self._serverCmp)
109
minPriority=self.servers[0][0]
111
weightIndex = zip(xrange(len(self.servers)), [x[1] for x in self.servers
112
if x[0]==minPriority])
113
weightSum = reduce(lambda x, y: (None, x[1]+y[1]), weightIndex, (None, 0))[1]
114
rand = random.randint(0, weightSum)
116
for index, weight in weightIndex:
119
chosen = self.servers[index]
120
del self.servers[index]
121
self.orderedServers.append(chosen)
123
p, w, host, port = chosen
126
raise RuntimeError, 'Impossible %s pickServer result.' % self.__class__.__name__
128
def _reallyConnect(self):
129
if self.stopAfterDNS:
133
self.host, self.port = self.pickServer()
134
assert self.host is not None, 'Must have a host to connect to.'
135
assert self.port is not None, 'Must have a port to connect to.'
137
connectFunc = getattr(self.reactor, self.connectFuncName)
138
self.connector=connectFunc(
139
self.host, self.port,
140
_SRVConnector_ClientFactoryWrapper(self, self.factory),
141
*self.connectFuncArgs, **self.connectFuncKwArgs)
143
def stopConnecting(self):
144
"""Stop attempting to connect."""
146
self.connector.stopConnecting()
150
def disconnect(self):
151
"""Disconnect whatever our are state is."""
152
if self.connector is not None:
153
self.connector.disconnect()
155
self.stopConnecting()
157
def getDestination(self):
158
assert self.connector
159
return self.connector.getDestination()
161
def connectionFailed(self, reason):
162
self.factory.clientConnectionFailed(self, reason)
163
self.factory.doStop()
165
def connectionLost(self, reason):
166
self.factory.clientConnectionLost(self, reason)
167
self.factory.doStop()