aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py3/twisted/names/srvconnect.py
diff options
context:
space:
mode:
authorshmel1k <shmel1k@ydb.tech>2023-11-26 18:16:14 +0300
committershmel1k <shmel1k@ydb.tech>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py3/twisted/names/srvconnect.py
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py3/twisted/names/srvconnect.py')
-rw-r--r--contrib/python/Twisted/py3/twisted/names/srvconnect.py271
1 files changed, 271 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py3/twisted/names/srvconnect.py b/contrib/python/Twisted/py3/twisted/names/srvconnect.py
new file mode 100644
index 0000000000..6cad7a2539
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/names/srvconnect.py
@@ -0,0 +1,271 @@
+# -*- test-case-name: twisted.names.test.test_srvconnect -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import random
+
+from zope.interface import implementer
+
+from twisted.internet import error, interfaces
+from twisted.names import client, dns
+from twisted.names.error import DNSNameError
+from twisted.python.compat import nativeString
+
+
+class _SRVConnector_ClientFactoryWrapper:
+ def __init__(self, connector, wrappedFactory):
+ self.__connector = connector
+ self.__wrappedFactory = wrappedFactory
+
+ def startedConnecting(self, connector):
+ self.__wrappedFactory.startedConnecting(self.__connector)
+
+ def clientConnectionFailed(self, connector, reason):
+ self.__connector.connectionFailed(reason)
+
+ def clientConnectionLost(self, connector, reason):
+ self.__connector.connectionLost(reason)
+
+ def __getattr__(self, key):
+ return getattr(self.__wrappedFactory, key)
+
+
+@implementer(interfaces.IConnector)
+class SRVConnector:
+ """
+ A connector that looks up DNS SRV records.
+
+ RFC 2782 details how SRV records should be interpreted and selected
+ for subsequent connection attempts. The algorithm for using the records'
+ priority and weight is implemented in L{pickServer}.
+
+ @ivar servers: List of candidate server records for future connection
+ attempts.
+ @type servers: L{list} of L{dns.Record_SRV}
+
+ @ivar orderedServers: List of server records that have already been tried
+ in this round of connection attempts.
+ @type orderedServers: L{list} of L{dns.Record_SRV}
+ """
+
+ stopAfterDNS = 0
+
+ def __init__(
+ self,
+ reactor,
+ service,
+ domain,
+ factory,
+ protocol="tcp",
+ connectFuncName="connectTCP",
+ connectFuncArgs=(),
+ connectFuncKwArgs={},
+ defaultPort=None,
+ ):
+ """
+ @param domain: The domain to connect to. If passed as a text
+ string, it will be encoded using C{idna} encoding.
+ @type domain: L{bytes} or L{str}
+
+ @param defaultPort: Optional default port number to be used when SRV
+ lookup fails and the service name is unknown. This should be the
+ port number associated with the service name as defined by the IANA
+ registry.
+ @type defaultPort: L{int}
+ """
+ self.reactor = reactor
+ self.service = service
+ self.domain = None if domain is None else dns.domainString(domain)
+ self.factory = factory
+
+ self.protocol = protocol
+ self.connectFuncName = connectFuncName
+ self.connectFuncArgs = connectFuncArgs
+ self.connectFuncKwArgs = connectFuncKwArgs
+ self._defaultPort = defaultPort
+
+ self.connector = None
+ self.servers = None
+ # list of servers already used in this round:
+ self.orderedServers = None
+
+ def connect(self):
+ """Start connection to remote server."""
+ self.factory.doStart()
+ self.factory.startedConnecting(self)
+
+ if not self.servers:
+ if self.domain is None:
+ self.connectionFailed(
+ error.DNSLookupError("Domain is not defined."),
+ )
+ return
+ d = client.lookupService(
+ "_%s._%s.%s"
+ % (
+ nativeString(self.service),
+ nativeString(self.protocol),
+ nativeString(self.domain),
+ ),
+ )
+ d.addCallbacks(self._cbGotServers, self._ebGotServers)
+ d.addCallback(lambda x, self=self: self._reallyConnect())
+ if self._defaultPort:
+ d.addErrback(self._ebServiceUnknown)
+ d.addErrback(self.connectionFailed)
+ elif self.connector is None:
+ self._reallyConnect()
+ else:
+ self.connector.connect()
+
+ def _ebGotServers(self, failure):
+ failure.trap(DNSNameError)
+
+ # Some DNS servers reply with NXDOMAIN when in fact there are
+ # just no SRV records for that domain. Act as if we just got an
+ # empty response and use fallback.
+
+ self.servers = []
+ self.orderedServers = []
+
+ def _cbGotServers(self, result):
+ answers, auth, add = result
+ if (
+ len(answers) == 1
+ and answers[0].type == dns.SRV
+ and answers[0].payload
+ and answers[0].payload.target == dns.Name(b".")
+ ):
+ # decidedly not available
+ raise error.DNSLookupError(
+ "Service %s not available for domain %s."
+ % (repr(self.service), repr(self.domain))
+ )
+
+ self.servers = []
+ self.orderedServers = []
+ for a in answers:
+ if a.type != dns.SRV or not a.payload:
+ continue
+
+ self.orderedServers.append(a.payload)
+
+ def _ebServiceUnknown(self, failure):
+ """
+ Connect to the default port when the service name is unknown.
+
+ If no SRV records were found, the service name will be passed as the
+ port. If resolving the name fails with
+ L{error.ServiceNameUnknownError}, a final attempt is done using the
+ default port.
+ """
+ failure.trap(error.ServiceNameUnknownError)
+ self.servers = [dns.Record_SRV(0, 0, self._defaultPort, self.domain)]
+ self.orderedServers = []
+ self.connect()
+
+ def pickServer(self):
+ """
+ Pick the next server.
+
+ This selects the next server from the list of SRV records according
+ to their priority and weight values, as set out by the default
+ algorithm specified in RFC 2782.
+
+ At the beginning of a round, L{servers} is populated with
+ L{orderedServers}, and the latter is made empty. L{servers}
+ is the list of candidates, and L{orderedServers} is the list of servers
+ that have already been tried.
+
+ First, all records are ordered by priority and weight in ascending
+ order. Then for each priority level, a running sum is calculated
+ over the sorted list of records for that priority. Then a random value
+ between 0 and the final sum is compared to each record in order. The
+ first record that is greater than or equal to that random value is
+ chosen and removed from the list of candidates for this round.
+
+ @return: A tuple of target hostname and port from the chosen DNS SRV
+ record.
+ @rtype: L{tuple} of native L{str} and L{int}
+ """
+ assert self.servers is not None
+ assert self.orderedServers is not None
+
+ if not self.servers and not self.orderedServers:
+ # no SRV record, fall back..
+ return nativeString(self.domain), self.service
+
+ if not self.servers and self.orderedServers:
+ # start new round
+ self.servers = self.orderedServers
+ self.orderedServers = []
+
+ assert self.servers
+
+ self.servers.sort(key=lambda record: (record.priority, record.weight))
+ minPriority = self.servers[0].priority
+
+ index = 0
+ weightSum = 0
+ weightIndex = []
+ for x in self.servers:
+ if x.priority == minPriority:
+ weightSum += x.weight
+ weightIndex.append((index, weightSum))
+ index += 1
+
+ rand = random.randint(0, weightSum)
+ for index, weight in weightIndex:
+ if weight >= rand:
+ chosen = self.servers[index]
+ del self.servers[index]
+ self.orderedServers.append(chosen)
+
+ return str(chosen.target), chosen.port
+
+ raise RuntimeError(f"Impossible {self.__class__.__name__} pickServer result.")
+
+ def _reallyConnect(self):
+ if self.stopAfterDNS:
+ self.stopAfterDNS = 0
+ return
+
+ self.host, self.port = self.pickServer()
+ assert self.host is not None, "Must have a host to connect to."
+ assert self.port is not None, "Must have a port to connect to."
+
+ connectFunc = getattr(self.reactor, self.connectFuncName)
+ self.connector = connectFunc(
+ self.host,
+ self.port,
+ _SRVConnector_ClientFactoryWrapper(self, self.factory),
+ *self.connectFuncArgs,
+ **self.connectFuncKwArgs,
+ )
+
+ def stopConnecting(self):
+ """Stop attempting to connect."""
+ if self.connector:
+ self.connector.stopConnecting()
+ else:
+ self.stopAfterDNS = 1
+
+ def disconnect(self):
+ """Disconnect whatever our are state is."""
+ if self.connector is not None:
+ self.connector.disconnect()
+ else:
+ self.stopConnecting()
+
+ def getDestination(self):
+ assert self.connector
+ return self.connector.getDestination()
+
+ def connectionFailed(self, reason):
+ self.factory.clientConnectionFailed(self, reason)
+ self.factory.doStop()
+
+ def connectionLost(self, reason):
+ self.factory.clientConnectionLost(self, reason)
+ self.factory.doStop()