17.12
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
|
||||
"""The Finger User Information Protocol (RFC 1288)"""
|
||||
|
||||
from twisted.protocols import basic
|
||||
|
||||
class Finger(basic.LineReceiver):
|
||||
|
||||
def lineReceived(self, line):
|
||||
parts = line.split()
|
||||
if not parts:
|
||||
parts = [b'']
|
||||
if len(parts) == 1:
|
||||
slash_w = 0
|
||||
else:
|
||||
slash_w = 1
|
||||
user = parts[-1]
|
||||
if b'@' in user:
|
||||
hostPlace = user.rfind(b'@')
|
||||
user = user[:hostPlace]
|
||||
host = user[hostPlace+1:]
|
||||
return self.forwardQuery(slash_w, user, host)
|
||||
if user:
|
||||
return self.getUser(slash_w, user)
|
||||
else:
|
||||
return self.getDomain(slash_w)
|
||||
|
||||
def _refuseMessage(self, message):
|
||||
self.transport.write(message + b"\n")
|
||||
self.transport.loseConnection()
|
||||
|
||||
def forwardQuery(self, slash_w, user, host):
|
||||
self._refuseMessage(b'Finger forwarding service denied')
|
||||
|
||||
def getDomain(self, slash_w):
|
||||
self._refuseMessage(b'Finger online list denied')
|
||||
|
||||
def getUser(self, slash_w, user):
|
||||
self.transport.write(b'Login: ' + user + b'\n')
|
||||
self._refuseMessage(b'No such user')
|
||||
@@ -0,0 +1,71 @@
|
||||
# -*- test-case-name: twisted.protocols.haproxy.test.test_parser -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Parser for 'haproxy:' string endpoint.
|
||||
"""
|
||||
|
||||
from zope.interface import implementer
|
||||
from twisted.plugin import IPlugin
|
||||
|
||||
from twisted.internet.endpoints import (
|
||||
quoteStringArgument, serverFromString, IStreamServerEndpointStringParser
|
||||
)
|
||||
from twisted.python.compat import iteritems
|
||||
|
||||
from . import proxyEndpoint
|
||||
|
||||
|
||||
def unparseEndpoint(args, kwargs):
|
||||
"""
|
||||
Un-parse the already-parsed args and kwargs back into endpoint syntax.
|
||||
|
||||
@param args: C{:}-separated arguments
|
||||
@type args: L{tuple} of native L{str}
|
||||
|
||||
@param kwargs: C{:} and then C{=}-separated keyword arguments
|
||||
|
||||
@type arguments: L{tuple} of native L{str}
|
||||
|
||||
@return: a string equivalent to the original format which this was parsed
|
||||
as.
|
||||
@rtype: native L{str}
|
||||
"""
|
||||
|
||||
description = ':'.join(
|
||||
[quoteStringArgument(str(arg)) for arg in args] +
|
||||
sorted(['%s=%s' % (quoteStringArgument(str(key)),
|
||||
quoteStringArgument(str(value)))
|
||||
for key, value in iteritems(kwargs)
|
||||
]))
|
||||
return description
|
||||
|
||||
|
||||
|
||||
@implementer(IPlugin, IStreamServerEndpointStringParser)
|
||||
class HAProxyServerParser(object):
|
||||
"""
|
||||
Stream server endpoint string parser for the HAProxyServerEndpoint type.
|
||||
|
||||
@ivar prefix: See L{IStreamServerEndpointStringParser.prefix}.
|
||||
"""
|
||||
prefix = "haproxy"
|
||||
|
||||
def parseStreamServer(self, reactor, *args, **kwargs):
|
||||
"""
|
||||
Parse a stream server endpoint from a reactor and string-only arguments
|
||||
and keyword arguments.
|
||||
|
||||
@param reactor: The reactor.
|
||||
|
||||
@param args: The parsed string arguments.
|
||||
|
||||
@param kwargs: The parsed keyword arguments.
|
||||
|
||||
@return: a stream server endpoint
|
||||
@rtype: L{IStreamServerEndpoint}
|
||||
"""
|
||||
subdescription = unparseEndpoint(args, kwargs)
|
||||
wrappedEndpoint = serverFromString(reactor, subdescription)
|
||||
return proxyEndpoint(wrappedEndpoint)
|
||||
@@ -0,0 +1,143 @@
|
||||
# -*- test-case-name: twisted.protocols.haproxy.test.test_v1parser -*-
|
||||
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
IProxyParser implementation for version one of the PROXY protocol.
|
||||
"""
|
||||
|
||||
from zope.interface import implementer
|
||||
from twisted.internet import address
|
||||
|
||||
from ._exceptions import (
|
||||
convertError, InvalidProxyHeader, InvalidNetworkProtocol,
|
||||
MissingAddressData
|
||||
)
|
||||
from . import _info
|
||||
from . import _interfaces
|
||||
|
||||
|
||||
|
||||
@implementer(_interfaces.IProxyParser)
|
||||
class V1Parser(object):
|
||||
"""
|
||||
PROXY protocol version one header parser.
|
||||
|
||||
Version one of the PROXY protocol is a human readable format represented
|
||||
by a single, newline delimited binary string that contains all of the
|
||||
relevant source and destination data.
|
||||
"""
|
||||
|
||||
PROXYSTR = b'PROXY'
|
||||
UNKNOWN_PROTO = b'UNKNOWN'
|
||||
TCP4_PROTO = b'TCP4'
|
||||
TCP6_PROTO = b'TCP6'
|
||||
ALLOWED_NET_PROTOS = (
|
||||
TCP4_PROTO,
|
||||
TCP6_PROTO,
|
||||
UNKNOWN_PROTO,
|
||||
)
|
||||
NEWLINE = b'\r\n'
|
||||
|
||||
def __init__(self):
|
||||
self.buffer = b''
|
||||
|
||||
|
||||
def feed(self, data):
|
||||
"""
|
||||
Consume a chunk of data and attempt to parse it.
|
||||
|
||||
@param data: A bytestring.
|
||||
@type data: L{bytes}
|
||||
|
||||
@return: A two-tuple containing, in order, a
|
||||
L{_interfaces.IProxyInfo} and any bytes fed to the
|
||||
parser that followed the end of the header. Both of these values
|
||||
are None until a complete header is parsed.
|
||||
|
||||
@raises InvalidProxyHeader: If the bytes fed to the parser create an
|
||||
invalid PROXY header.
|
||||
"""
|
||||
self.buffer += data
|
||||
if len(self.buffer) > 107 and self.NEWLINE not in self.buffer:
|
||||
raise InvalidProxyHeader()
|
||||
lines = (self.buffer).split(self.NEWLINE, 1)
|
||||
if not len(lines) > 1:
|
||||
return (None, None)
|
||||
self.buffer = b''
|
||||
remaining = lines.pop()
|
||||
header = lines.pop()
|
||||
info = self.parse(header)
|
||||
return (info, remaining)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse(cls, line):
|
||||
"""
|
||||
Parse a bytestring as a full PROXY protocol header line.
|
||||
|
||||
@param line: A bytestring that represents a valid HAProxy PROXY
|
||||
protocol header line.
|
||||
@type line: bytes
|
||||
|
||||
@return: A L{_interfaces.IProxyInfo} containing the parsed data.
|
||||
|
||||
@raises InvalidProxyHeader: If the bytestring does not represent a
|
||||
valid PROXY header.
|
||||
|
||||
@raises InvalidNetworkProtocol: When no protocol can be parsed or is
|
||||
not one of the allowed values.
|
||||
|
||||
@raises MissingAddressData: When the protocol is TCP* but the header
|
||||
does not contain a complete set of addresses and ports.
|
||||
"""
|
||||
originalLine = line
|
||||
proxyStr = None
|
||||
networkProtocol = None
|
||||
sourceAddr = None
|
||||
sourcePort = None
|
||||
destAddr = None
|
||||
destPort = None
|
||||
|
||||
with convertError(ValueError, InvalidProxyHeader):
|
||||
proxyStr, line = line.split(b' ', 1)
|
||||
|
||||
if proxyStr != cls.PROXYSTR:
|
||||
raise InvalidProxyHeader()
|
||||
|
||||
with convertError(ValueError, InvalidNetworkProtocol):
|
||||
networkProtocol, line = line.split(b' ', 1)
|
||||
|
||||
if networkProtocol not in cls.ALLOWED_NET_PROTOS:
|
||||
raise InvalidNetworkProtocol()
|
||||
|
||||
if networkProtocol == cls.UNKNOWN_PROTO:
|
||||
|
||||
return _info.ProxyInfo(originalLine, None, None)
|
||||
|
||||
with convertError(ValueError, MissingAddressData):
|
||||
sourceAddr, line = line.split(b' ', 1)
|
||||
|
||||
with convertError(ValueError, MissingAddressData):
|
||||
destAddr, line = line.split(b' ', 1)
|
||||
|
||||
with convertError(ValueError, MissingAddressData):
|
||||
sourcePort, line = line.split(b' ', 1)
|
||||
|
||||
with convertError(ValueError, MissingAddressData):
|
||||
destPort = line.split(b' ')[0]
|
||||
|
||||
if networkProtocol == cls.TCP4_PROTO:
|
||||
|
||||
return _info.ProxyInfo(
|
||||
originalLine,
|
||||
address.IPv4Address('TCP', sourceAddr, int(sourcePort)),
|
||||
address.IPv4Address('TCP', destAddr, int(destPort)),
|
||||
)
|
||||
|
||||
return _info.ProxyInfo(
|
||||
originalLine,
|
||||
address.IPv6Address('TCP', sourceAddr, int(sourcePort)),
|
||||
address.IPv6Address('TCP', destAddr, int(destPort)),
|
||||
)
|
||||
@@ -0,0 +1,106 @@
|
||||
# -*- test-case-name: twisted.protocols.haproxy.test.test_wrapper -*-
|
||||
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Protocol wrapper that provides HAProxy PROXY protocol support.
|
||||
"""
|
||||
|
||||
from twisted.protocols import policies
|
||||
from twisted.internet import interfaces
|
||||
from twisted.internet.endpoints import _WrapperServerEndpoint
|
||||
|
||||
from ._exceptions import InvalidProxyHeader
|
||||
from ._v1parser import V1Parser
|
||||
from ._v2parser import V2Parser
|
||||
|
||||
|
||||
|
||||
class HAProxyProtocolWrapper(policies.ProtocolWrapper, object):
|
||||
"""
|
||||
A Protocol wrapper that provides HAProxy support.
|
||||
|
||||
This protocol reads the PROXY stream header, v1 or v2, parses the provided
|
||||
connection data, and modifies the behavior of getPeer and getHost to return
|
||||
the data provided by the PROXY header.
|
||||
"""
|
||||
|
||||
def __init__(self, factory, wrappedProtocol):
|
||||
policies.ProtocolWrapper.__init__(self, factory, wrappedProtocol)
|
||||
self._proxyInfo = None
|
||||
self._parser = None
|
||||
|
||||
|
||||
def dataReceived(self, data):
|
||||
if self._proxyInfo is not None:
|
||||
return self.wrappedProtocol.dataReceived(data)
|
||||
|
||||
if self._parser is None:
|
||||
if (
|
||||
len(data) >= 16 and
|
||||
data[:12] == V2Parser.PREFIX and
|
||||
ord(data[12:13]) & 0b11110000 == 0x20
|
||||
):
|
||||
self._parser = V2Parser()
|
||||
elif len(data) >= 8 and data[:5] == V1Parser.PROXYSTR:
|
||||
self._parser = V1Parser()
|
||||
else:
|
||||
self.loseConnection()
|
||||
return None
|
||||
|
||||
try:
|
||||
self._proxyInfo, remaining = self._parser.feed(data)
|
||||
if remaining:
|
||||
self.wrappedProtocol.dataReceived(remaining)
|
||||
except InvalidProxyHeader:
|
||||
self.loseConnection()
|
||||
|
||||
|
||||
def getPeer(self):
|
||||
if self._proxyInfo and self._proxyInfo.source:
|
||||
return self._proxyInfo.source
|
||||
return self.transport.getPeer()
|
||||
|
||||
|
||||
def getHost(self):
|
||||
if self._proxyInfo and self._proxyInfo.destination:
|
||||
return self._proxyInfo.destination
|
||||
return self.transport.getHost()
|
||||
|
||||
|
||||
|
||||
class HAProxyWrappingFactory(policies.WrappingFactory):
|
||||
"""
|
||||
A Factory wrapper that adds PROXY protocol support to connections.
|
||||
"""
|
||||
protocol = HAProxyProtocolWrapper
|
||||
|
||||
def logPrefix(self):
|
||||
"""
|
||||
Annotate the wrapped factory's log prefix with some text indicating
|
||||
the PROXY protocol is in use.
|
||||
|
||||
@rtype: C{str}
|
||||
"""
|
||||
if interfaces.ILoggingContext.providedBy(self.wrappedFactory):
|
||||
logPrefix = self.wrappedFactory.logPrefix()
|
||||
else:
|
||||
logPrefix = self.wrappedFactory.__class__.__name__
|
||||
return "%s (PROXY)" % (logPrefix,)
|
||||
|
||||
|
||||
|
||||
def proxyEndpoint(wrappedEndpoint):
|
||||
"""
|
||||
Wrap an endpoint with PROXY protocol support, so that the transport's
|
||||
C{getHost} and C{getPeer} methods reflect the attributes of the proxied
|
||||
connection rather than the underlying connection.
|
||||
|
||||
@param wrappedEndpoint: The underlying listening endpoint.
|
||||
@type wrappedEndpoint: L{IStreamServerEndpoint}
|
||||
|
||||
@return: a new listening endpoint that speaks the PROXY protocol.
|
||||
@rtype: L{IStreamServerEndpoint}
|
||||
"""
|
||||
return _WrapperServerEndpoint(wrappedEndpoint, HAProxyWrappingFactory)
|
||||
@@ -0,0 +1,7 @@
|
||||
# -*- test-case-name: twisted.protocols.haproxy.test -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Unit tests for L{twisted.protocols.haproxy}.
|
||||
"""
|
||||
@@ -0,0 +1,295 @@
|
||||
# -*- test-case-name: twisted.test.test_htb -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
|
||||
"""
|
||||
Hierarchical Token Bucket traffic shaping.
|
||||
|
||||
Patterned after U{Martin Devera's Hierarchical Token Bucket traffic
|
||||
shaper for the Linux kernel<http://luxik.cdi.cz/~devik/qos/htb/>}.
|
||||
|
||||
@seealso: U{HTB Linux queuing discipline manual - user guide
|
||||
<http://luxik.cdi.cz/~devik/qos/htb/manual/userg.htm>}
|
||||
@seealso: U{Token Bucket Filter in Linux Advanced Routing & Traffic Control
|
||||
HOWTO<http://lartc.org/howto/lartc.qdisc.classless.html#AEN682>}
|
||||
"""
|
||||
|
||||
|
||||
# TODO: Investigate whether we should be using os.times()[-1] instead of
|
||||
# time.time. time.time, it has been pointed out, can go backwards. Is
|
||||
# the same true of os.times?
|
||||
from time import time
|
||||
from zope.interface import implementer, Interface
|
||||
|
||||
from twisted.protocols import pcp
|
||||
|
||||
|
||||
class Bucket:
|
||||
"""
|
||||
Implementation of a Token bucket.
|
||||
|
||||
A bucket can hold a certain number of tokens and it drains over time.
|
||||
|
||||
@cvar maxburst: The maximum number of tokens that the bucket can
|
||||
hold at any given time. If this is L{None}, the bucket has
|
||||
an infinite size.
|
||||
@type maxburst: C{int}
|
||||
@cvar rate: The rate at which the bucket drains, in number
|
||||
of tokens per second. If the rate is L{None}, the bucket
|
||||
drains instantaneously.
|
||||
@type rate: C{int}
|
||||
"""
|
||||
|
||||
maxburst = None
|
||||
rate = None
|
||||
|
||||
_refcount = 0
|
||||
|
||||
def __init__(self, parentBucket=None):
|
||||
"""
|
||||
Create a L{Bucket} that may have a parent L{Bucket}.
|
||||
|
||||
@param parentBucket: If a parent Bucket is specified,
|
||||
all L{add} and L{drip} operations on this L{Bucket}
|
||||
will be applied on the parent L{Bucket} as well.
|
||||
@type parentBucket: L{Bucket}
|
||||
"""
|
||||
self.content = 0
|
||||
self.parentBucket = parentBucket
|
||||
self.lastDrip = time()
|
||||
|
||||
|
||||
def add(self, amount):
|
||||
"""
|
||||
Adds tokens to the L{Bucket} and its C{parentBucket}.
|
||||
|
||||
This will add as many of the C{amount} tokens as will fit into both
|
||||
this L{Bucket} and its C{parentBucket}.
|
||||
|
||||
@param amount: The number of tokens to try to add.
|
||||
@type amount: C{int}
|
||||
|
||||
@returns: The number of tokens that actually fit.
|
||||
@returntype: C{int}
|
||||
"""
|
||||
self.drip()
|
||||
if self.maxburst is None:
|
||||
allowable = amount
|
||||
else:
|
||||
allowable = min(amount, self.maxburst - self.content)
|
||||
|
||||
if self.parentBucket is not None:
|
||||
allowable = self.parentBucket.add(allowable)
|
||||
self.content += allowable
|
||||
return allowable
|
||||
|
||||
|
||||
def drip(self):
|
||||
"""
|
||||
Let some of the bucket drain.
|
||||
|
||||
The L{Bucket} drains at the rate specified by the class
|
||||
variable C{rate}.
|
||||
|
||||
@returns: C{True} if the bucket is empty after this drip.
|
||||
@returntype: C{bool}
|
||||
"""
|
||||
if self.parentBucket is not None:
|
||||
self.parentBucket.drip()
|
||||
|
||||
if self.rate is None:
|
||||
self.content = 0
|
||||
else:
|
||||
now = time()
|
||||
deltaTime = now - self.lastDrip
|
||||
deltaTokens = deltaTime * self.rate
|
||||
self.content = max(0, self.content - deltaTokens)
|
||||
self.lastDrip = now
|
||||
return self.content == 0
|
||||
|
||||
|
||||
class IBucketFilter(Interface):
|
||||
def getBucketFor(*somethings, **some_kw):
|
||||
"""
|
||||
Return a L{Bucket} corresponding to the provided parameters.
|
||||
|
||||
@returntype: L{Bucket}
|
||||
"""
|
||||
|
||||
@implementer(IBucketFilter)
|
||||
class HierarchicalBucketFilter:
|
||||
"""
|
||||
Filter things into buckets that can be nested.
|
||||
|
||||
@cvar bucketFactory: Class of buckets to make.
|
||||
@type bucketFactory: L{Bucket}
|
||||
@cvar sweepInterval: Seconds between sweeping out the bucket cache.
|
||||
@type sweepInterval: C{int}
|
||||
"""
|
||||
bucketFactory = Bucket
|
||||
sweepInterval = None
|
||||
|
||||
def __init__(self, parentFilter=None):
|
||||
self.buckets = {}
|
||||
self.parentFilter = parentFilter
|
||||
self.lastSweep = time()
|
||||
|
||||
def getBucketFor(self, *a, **kw):
|
||||
"""
|
||||
Find or create a L{Bucket} corresponding to the provided parameters.
|
||||
|
||||
Any parameters are passed on to L{getBucketKey}, from them it
|
||||
decides which bucket you get.
|
||||
|
||||
@returntype: L{Bucket}
|
||||
"""
|
||||
if ((self.sweepInterval is not None)
|
||||
and ((time() - self.lastSweep) > self.sweepInterval)):
|
||||
self.sweep()
|
||||
|
||||
if self.parentFilter:
|
||||
parentBucket = self.parentFilter.getBucketFor(self, *a, **kw)
|
||||
else:
|
||||
parentBucket = None
|
||||
|
||||
key = self.getBucketKey(*a, **kw)
|
||||
bucket = self.buckets.get(key)
|
||||
if bucket is None:
|
||||
bucket = self.bucketFactory(parentBucket)
|
||||
self.buckets[key] = bucket
|
||||
return bucket
|
||||
|
||||
def getBucketKey(self, *a, **kw):
|
||||
"""
|
||||
Construct a key based on the input parameters to choose a L{Bucket}.
|
||||
|
||||
The default implementation returns the same key for all
|
||||
arguments. Override this method to provide L{Bucket} selection.
|
||||
|
||||
@returns: Something to be used as a key in the bucket cache.
|
||||
"""
|
||||
return None
|
||||
|
||||
def sweep(self):
|
||||
"""
|
||||
Remove empty buckets.
|
||||
"""
|
||||
for key, bucket in self.buckets.items():
|
||||
bucket_is_empty = bucket.drip()
|
||||
if (bucket._refcount == 0) and bucket_is_empty:
|
||||
del self.buckets[key]
|
||||
|
||||
self.lastSweep = time()
|
||||
|
||||
|
||||
class FilterByHost(HierarchicalBucketFilter):
|
||||
"""
|
||||
A Hierarchical Bucket filter with a L{Bucket} for each host.
|
||||
"""
|
||||
sweepInterval = 60 * 20
|
||||
|
||||
def getBucketKey(self, transport):
|
||||
return transport.getPeer()[1]
|
||||
|
||||
|
||||
class FilterByServer(HierarchicalBucketFilter):
|
||||
"""
|
||||
A Hierarchical Bucket filter with a L{Bucket} for each service.
|
||||
"""
|
||||
sweepInterval = None
|
||||
|
||||
def getBucketKey(self, transport):
|
||||
return transport.getHost()[2]
|
||||
|
||||
|
||||
class ShapedConsumer(pcp.ProducerConsumerProxy):
|
||||
"""
|
||||
Wraps a C{Consumer} and shapes the rate at which it receives data.
|
||||
"""
|
||||
# Providing a Pull interface means I don't have to try to schedule
|
||||
# traffic with callLaters.
|
||||
iAmStreaming = False
|
||||
|
||||
def __init__(self, consumer, bucket):
|
||||
pcp.ProducerConsumerProxy.__init__(self, consumer)
|
||||
self.bucket = bucket
|
||||
self.bucket._refcount += 1
|
||||
|
||||
def _writeSomeData(self, data):
|
||||
# In practice, this actually results in obscene amounts of
|
||||
# overhead, as a result of generating lots and lots of packets
|
||||
# with twelve-byte payloads. We may need to do a version of
|
||||
# this with scheduled writes after all.
|
||||
amount = self.bucket.add(len(data))
|
||||
return pcp.ProducerConsumerProxy._writeSomeData(self, data[:amount])
|
||||
|
||||
def stopProducing(self):
|
||||
pcp.ProducerConsumerProxy.stopProducing(self)
|
||||
self.bucket._refcount -= 1
|
||||
|
||||
|
||||
class ShapedTransport(ShapedConsumer):
|
||||
"""
|
||||
Wraps a C{Transport} and shapes the rate at which it receives data.
|
||||
|
||||
This is a L{ShapedConsumer} with a little bit of magic to provide for
|
||||
the case where the consumer it wraps is also a C{Transport} and people
|
||||
will be attempting to access attributes this does not proxy as a
|
||||
C{Consumer} (e.g. C{loseConnection}).
|
||||
"""
|
||||
# Ugh. We only wanted to filter IConsumer, not ITransport.
|
||||
|
||||
iAmStreaming = False
|
||||
def __getattr__(self, name):
|
||||
# Because people will be doing things like .getPeer and
|
||||
# .loseConnection on me.
|
||||
return getattr(self.consumer, name)
|
||||
|
||||
|
||||
class ShapedProtocolFactory:
|
||||
"""
|
||||
Dispense C{Protocols} with traffic shaping on their transports.
|
||||
|
||||
Usage::
|
||||
|
||||
myserver = SomeFactory()
|
||||
myserver.protocol = ShapedProtocolFactory(myserver.protocol,
|
||||
bucketFilter)
|
||||
|
||||
Where C{SomeServerFactory} is a L{twisted.internet.protocol.Factory}, and
|
||||
C{bucketFilter} is an instance of L{HierarchicalBucketFilter}.
|
||||
"""
|
||||
def __init__(self, protoClass, bucketFilter):
|
||||
"""
|
||||
Tell me what to wrap and where to get buckets.
|
||||
|
||||
@param protoClass: The class of C{Protocol} this will generate
|
||||
wrapped instances of.
|
||||
@type protoClass: L{Protocol<twisted.internet.interfaces.IProtocol>}
|
||||
class
|
||||
@param bucketFilter: The filter which will determine how
|
||||
traffic is shaped.
|
||||
@type bucketFilter: L{HierarchicalBucketFilter}.
|
||||
"""
|
||||
# More precisely, protoClass can be any callable that will return
|
||||
# instances of something that implements IProtocol.
|
||||
self.protocol = protoClass
|
||||
self.bucketFilter = bucketFilter
|
||||
|
||||
def __call__(self, *a, **kw):
|
||||
"""
|
||||
Make a C{Protocol} instance with a shaped transport.
|
||||
|
||||
Any parameters will be passed on to the protocol's initializer.
|
||||
|
||||
@returns: A C{Protocol} instance with a L{ShapedTransport}.
|
||||
"""
|
||||
proto = self.protocol(*a, **kw)
|
||||
origMakeConnection = proto.makeConnection
|
||||
def makeConnection(transport):
|
||||
bucket = self.bucketFilter.getBucketFor(transport)
|
||||
shapedTransport = ShapedTransport(transport, bucket)
|
||||
return origMakeConnection(shapedTransport)
|
||||
proto.makeConnection = makeConnection
|
||||
return proto
|
||||
@@ -0,0 +1,158 @@
|
||||
# -*- test-case-name: twisted.test.test_postfix -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Postfix mail transport agent related protocols.
|
||||
"""
|
||||
|
||||
import sys
|
||||
try:
|
||||
# Python 2
|
||||
from UserDict import UserDict
|
||||
except ImportError:
|
||||
# Python 3
|
||||
from collections import UserDict
|
||||
|
||||
try:
|
||||
# Python 2
|
||||
from urllib import quote as _quote, unquote as _unquote
|
||||
except ImportError:
|
||||
# Python 3
|
||||
from urllib.parse import quote as _quote, unquote as _unquote
|
||||
|
||||
from twisted.protocols import basic
|
||||
from twisted.protocols import policies
|
||||
from twisted.internet import protocol, defer
|
||||
from twisted.python import log
|
||||
from twisted.python.compat import unicode
|
||||
|
||||
# urllib's quote functions just happen to match
|
||||
# the postfix semantics.
|
||||
def quote(s):
|
||||
quoted = _quote(s)
|
||||
if isinstance(quoted, unicode):
|
||||
quoted = quoted.encode("ascii")
|
||||
return quoted
|
||||
|
||||
|
||||
|
||||
def unquote(s):
|
||||
if isinstance(s, bytes):
|
||||
s = s.decode("ascii")
|
||||
quoted = _unquote(s)
|
||||
return quoted.encode("ascii")
|
||||
|
||||
|
||||
|
||||
class PostfixTCPMapServer(basic.LineReceiver, policies.TimeoutMixin):
|
||||
"""
|
||||
Postfix mail transport agent TCP map protocol implementation.
|
||||
|
||||
Receive requests for data matching given key via lineReceived,
|
||||
asks it's factory for the data with self.factory.get(key), and
|
||||
returns the data to the requester. None means no entry found.
|
||||
|
||||
You can use postfix's postmap to test the map service::
|
||||
|
||||
/usr/sbin/postmap -q KEY tcp:localhost:4242
|
||||
|
||||
"""
|
||||
|
||||
timeout = 600
|
||||
delimiter = b'\n'
|
||||
|
||||
def connectionMade(self):
|
||||
self.setTimeout(self.timeout)
|
||||
|
||||
|
||||
|
||||
def sendCode(self, code, message=b''):
|
||||
"""
|
||||
Send an SMTP-like code with a message.
|
||||
"""
|
||||
self.sendLine(str(code).encode("ascii") + b' ' + message)
|
||||
|
||||
|
||||
|
||||
def lineReceived(self, line):
|
||||
self.resetTimeout()
|
||||
try:
|
||||
request, params = line.split(None, 1)
|
||||
except ValueError:
|
||||
request = line
|
||||
params = None
|
||||
try:
|
||||
f = getattr(self, u'do_' + request.decode("ascii"))
|
||||
except AttributeError:
|
||||
self.sendCode(400, b'unknown command')
|
||||
else:
|
||||
try:
|
||||
f(params)
|
||||
except:
|
||||
excInfo = str(sys.exc_info()[1]).encode("ascii")
|
||||
self.sendCode(400, b'Command ' + request + b' failed: ' +
|
||||
excInfo)
|
||||
|
||||
|
||||
|
||||
def do_get(self, key):
|
||||
if key is None:
|
||||
self.sendCode(400, b"Command 'get' takes 1 parameters.")
|
||||
else:
|
||||
d = defer.maybeDeferred(self.factory.get, key)
|
||||
d.addCallbacks(self._cbGot, self._cbNot)
|
||||
d.addErrback(log.err)
|
||||
|
||||
|
||||
|
||||
def _cbNot(self, fail):
|
||||
msg = fail.getErrorMessage().encode("ascii")
|
||||
self.sendCode(400, msg)
|
||||
|
||||
|
||||
|
||||
def _cbGot(self, value):
|
||||
if value is None:
|
||||
self.sendCode(500)
|
||||
else:
|
||||
self.sendCode(200, quote(value))
|
||||
|
||||
|
||||
|
||||
def do_put(self, keyAndValue):
|
||||
if keyAndValue is None:
|
||||
self.sendCode(400, b"Command 'put' takes 2 parameters.")
|
||||
else:
|
||||
try:
|
||||
key, value = keyAndValue.split(None, 1)
|
||||
except ValueError:
|
||||
self.sendCode(400, b"Command 'put' takes 2 parameters.")
|
||||
else:
|
||||
self.sendCode(500, b'put is not implemented yet.')
|
||||
|
||||
|
||||
|
||||
class PostfixTCPMapDictServerFactory(UserDict, protocol.ServerFactory):
|
||||
"""
|
||||
An in-memory dictionary factory for PostfixTCPMapServer.
|
||||
"""
|
||||
|
||||
protocol = PostfixTCPMapServer
|
||||
|
||||
|
||||
|
||||
class PostfixTCPMapDeferringDictServerFactory(protocol.ServerFactory):
|
||||
"""
|
||||
An in-memory dictionary factory for PostfixTCPMapServer.
|
||||
"""
|
||||
|
||||
protocol = PostfixTCPMapServer
|
||||
|
||||
def __init__(self, data=None):
|
||||
self.data = {}
|
||||
if data is not None:
|
||||
self.data.update(data)
|
||||
|
||||
def get(self, key):
|
||||
return defer.succeed(self.data.get(key))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,255 @@
|
||||
# -*- test-case-name: twisted.test.test_socks -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Implementation of the SOCKSv4 protocol.
|
||||
"""
|
||||
|
||||
# python imports
|
||||
import struct
|
||||
import string
|
||||
import socket
|
||||
import time
|
||||
|
||||
# twisted imports
|
||||
from twisted.internet import reactor, protocol, defer
|
||||
from twisted.python import log
|
||||
|
||||
|
||||
class SOCKSv4Outgoing(protocol.Protocol):
|
||||
def __init__(self, socks):
|
||||
self.socks=socks
|
||||
|
||||
|
||||
def connectionMade(self):
|
||||
peer = self.transport.getPeer()
|
||||
self.socks.makeReply(90, 0, port=peer.port, ip=peer.host)
|
||||
self.socks.otherConn=self
|
||||
|
||||
|
||||
def connectionLost(self, reason):
|
||||
self.socks.transport.loseConnection()
|
||||
|
||||
|
||||
def dataReceived(self, data):
|
||||
self.socks.write(data)
|
||||
|
||||
|
||||
def write(self,data):
|
||||
self.socks.log(self,data)
|
||||
self.transport.write(data)
|
||||
|
||||
|
||||
|
||||
class SOCKSv4Incoming(protocol.Protocol):
|
||||
def __init__(self,socks):
|
||||
self.socks=socks
|
||||
self.socks.otherConn=self
|
||||
|
||||
|
||||
def connectionLost(self, reason):
|
||||
self.socks.transport.loseConnection()
|
||||
|
||||
|
||||
def dataReceived(self,data):
|
||||
self.socks.write(data)
|
||||
|
||||
|
||||
def write(self, data):
|
||||
self.socks.log(self,data)
|
||||
self.transport.write(data)
|
||||
|
||||
|
||||
|
||||
class SOCKSv4(protocol.Protocol):
|
||||
"""
|
||||
An implementation of the SOCKSv4 protocol.
|
||||
|
||||
@type logging: L{str} or L{None}
|
||||
@ivar logging: If not L{None}, the name of the logfile to which connection
|
||||
information will be written.
|
||||
|
||||
@type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
|
||||
@ivar reactor: The reactor used to create connections.
|
||||
|
||||
@type buf: L{str}
|
||||
@ivar buf: Part of a SOCKSv4 connection request.
|
||||
|
||||
@type otherConn: C{SOCKSv4Incoming}, C{SOCKSv4Outgoing} or L{None}
|
||||
@ivar otherConn: Until the connection has been established, C{otherConn} is
|
||||
L{None}. After that, it is the proxy-to-destination protocol instance
|
||||
along which the client's connection is being forwarded.
|
||||
"""
|
||||
def __init__(self, logging=None, reactor=reactor):
|
||||
self.logging = logging
|
||||
self.reactor = reactor
|
||||
|
||||
|
||||
def connectionMade(self):
|
||||
self.buf = b""
|
||||
self.otherConn = None
|
||||
|
||||
|
||||
def dataReceived(self, data):
|
||||
"""
|
||||
Called whenever data is received.
|
||||
|
||||
@type data: L{bytes}
|
||||
@param data: Part or all of a SOCKSv4 packet.
|
||||
"""
|
||||
if self.otherConn:
|
||||
self.otherConn.write(data)
|
||||
return
|
||||
self.buf = self.buf + data
|
||||
completeBuffer = self.buf
|
||||
if b"\000" in self.buf[8:]:
|
||||
head, self.buf = self.buf[:8], self.buf[8:]
|
||||
version, code, port = struct.unpack("!BBH", head[:4])
|
||||
user, self.buf = self.buf.split(b"\000", 1)
|
||||
if head[4:7] == b"\000\000\000" and head[7:8] != b"\000":
|
||||
# An IP address of the form 0.0.0.X, where X is non-zero,
|
||||
# signifies that this is a SOCKSv4a packet.
|
||||
# If the complete packet hasn't been received, restore the
|
||||
# buffer and wait for it.
|
||||
if b"\000" not in self.buf:
|
||||
self.buf = completeBuffer
|
||||
return
|
||||
server, self.buf = self.buf.split(b"\000", 1)
|
||||
d = self.reactor.resolve(server)
|
||||
d.addCallback(self._dataReceived2, user,
|
||||
version, code, port)
|
||||
d.addErrback(lambda result, self = self: self.makeReply(91))
|
||||
return
|
||||
else:
|
||||
server = socket.inet_ntoa(head[4:8])
|
||||
|
||||
self._dataReceived2(server, user, version, code, port)
|
||||
|
||||
|
||||
def _dataReceived2(self, server, user, version, code, port):
|
||||
"""
|
||||
The second half of the SOCKS connection setup. For a SOCKSv4 packet this
|
||||
is after the server address has been extracted from the header. For a
|
||||
SOCKSv4a packet this is after the host name has been resolved.
|
||||
|
||||
@type server: L{str}
|
||||
@param server: The IP address of the destination, represented as a
|
||||
dotted quad.
|
||||
|
||||
@type user: L{str}
|
||||
@param user: The username associated with the connection.
|
||||
|
||||
@type version: L{int}
|
||||
@param version: The SOCKS protocol version number.
|
||||
|
||||
@type code: L{int}
|
||||
@param code: The comand code. 1 means establish a TCP/IP stream
|
||||
connection, and 2 means establish a TCP/IP port binding.
|
||||
|
||||
@type port: L{int}
|
||||
@param port: The port number associated with the connection.
|
||||
"""
|
||||
assert version == 4, "Bad version code: %s" % version
|
||||
if not self.authorize(code, server, port, user):
|
||||
self.makeReply(91)
|
||||
return
|
||||
if code == 1: # CONNECT
|
||||
d = self.connectClass(server, port, SOCKSv4Outgoing, self)
|
||||
d.addErrback(lambda result, self = self: self.makeReply(91))
|
||||
elif code == 2: # BIND
|
||||
d = self.listenClass(0, SOCKSv4IncomingFactory, self, server)
|
||||
d.addCallback(lambda x,
|
||||
self = self: self.makeReply(90, 0, x[1], x[0]))
|
||||
else:
|
||||
raise RuntimeError("Bad Connect Code: %s" % (code,))
|
||||
assert self.buf == b"", "hmm, still stuff in buffer... %s" % repr(
|
||||
self.buf)
|
||||
|
||||
|
||||
def connectionLost(self, reason):
|
||||
if self.otherConn:
|
||||
self.otherConn.transport.loseConnection()
|
||||
|
||||
|
||||
def authorize(self,code,server,port,user):
|
||||
log.msg("code %s connection to %s:%s (user %s) authorized" % (code,server,port,user))
|
||||
return 1
|
||||
|
||||
|
||||
def connectClass(self, host, port, klass, *args):
|
||||
return protocol.ClientCreator(reactor, klass, *args).connectTCP(host,port)
|
||||
|
||||
|
||||
def listenClass(self, port, klass, *args):
|
||||
serv = reactor.listenTCP(port, klass(*args))
|
||||
return defer.succeed(serv.getHost()[1:])
|
||||
|
||||
|
||||
def makeReply(self,reply,version=0,port=0,ip="0.0.0.0"):
|
||||
self.transport.write(struct.pack("!BBH",version,reply,port)+socket.inet_aton(ip))
|
||||
if reply!=90: self.transport.loseConnection()
|
||||
|
||||
|
||||
def write(self,data):
|
||||
self.log(self,data)
|
||||
self.transport.write(data)
|
||||
|
||||
|
||||
def log(self,proto,data):
|
||||
if not self.logging: return
|
||||
peer = self.transport.getPeer()
|
||||
their_peer = self.otherConn.transport.getPeer()
|
||||
f=open(self.logging,"a")
|
||||
f.write("%s\t%s:%d %s %s:%d\n"%(time.ctime(),
|
||||
peer.host,peer.port,
|
||||
((proto==self and '<') or '>'),
|
||||
their_peer.host,their_peer.port))
|
||||
while data:
|
||||
p,data=data[:16],data[16:]
|
||||
f.write(string.join(map(lambda x:'%02X'%ord(x),p),' ')+' ')
|
||||
f.write((16-len(p))*3*' ')
|
||||
for c in p:
|
||||
if len(repr(c))>3: f.write('.')
|
||||
else: f.write(c)
|
||||
f.write('\n')
|
||||
f.write('\n')
|
||||
f.close()
|
||||
|
||||
|
||||
|
||||
class SOCKSv4Factory(protocol.Factory):
|
||||
"""
|
||||
A factory for a SOCKSv4 proxy.
|
||||
|
||||
Constructor accepts one argument, a log file name.
|
||||
"""
|
||||
def __init__(self, log):
|
||||
self.logging = log
|
||||
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
return SOCKSv4(self.logging, reactor)
|
||||
|
||||
|
||||
|
||||
class SOCKSv4IncomingFactory(protocol.Factory):
|
||||
"""
|
||||
A utility class for building protocols for incoming connections.
|
||||
"""
|
||||
def __init__(self, socks, ip):
|
||||
self.socks = socks
|
||||
self.ip = ip
|
||||
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
if addr[0] == self.ip:
|
||||
self.ip = ""
|
||||
self.socks.makeReply(90, 0)
|
||||
return SOCKSv4Incoming(self.socks)
|
||||
elif self.ip == "":
|
||||
return None
|
||||
else:
|
||||
self.socks.makeReply(91, 0)
|
||||
self.ip = ""
|
||||
return None
|
||||
@@ -0,0 +1,124 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""Implement standard (and unused) TCP protocols.
|
||||
|
||||
These protocols are either provided by inetd, or are not provided at all.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
import time
|
||||
import struct
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
from twisted.internet import protocol, interfaces
|
||||
|
||||
|
||||
|
||||
class Echo(protocol.Protocol):
|
||||
"""
|
||||
As soon as any data is received, write it back (RFC 862).
|
||||
"""
|
||||
|
||||
def dataReceived(self, data):
|
||||
self.transport.write(data)
|
||||
|
||||
|
||||
|
||||
class Discard(protocol.Protocol):
|
||||
"""
|
||||
Discard any received data (RFC 863).
|
||||
"""
|
||||
|
||||
def dataReceived(self, data):
|
||||
# I'm ignoring you, nyah-nyah
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@implementer(interfaces.IProducer)
|
||||
class Chargen(protocol.Protocol):
|
||||
"""
|
||||
Generate repeating noise (RFC 864).
|
||||
"""
|
||||
noise = b'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ !"#$%&?'
|
||||
|
||||
def connectionMade(self):
|
||||
self.transport.registerProducer(self, 0)
|
||||
|
||||
|
||||
def resumeProducing(self):
|
||||
self.transport.write(self.noise)
|
||||
|
||||
|
||||
def pauseProducing(self):
|
||||
pass
|
||||
|
||||
|
||||
def stopProducing(self):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class QOTD(protocol.Protocol):
|
||||
"""
|
||||
Return a quote of the day (RFC 865).
|
||||
"""
|
||||
|
||||
def connectionMade(self):
|
||||
self.transport.write(self.getQuote())
|
||||
self.transport.loseConnection()
|
||||
|
||||
|
||||
def getQuote(self):
|
||||
"""
|
||||
Return a quote. May be overrriden in subclasses.
|
||||
"""
|
||||
return b"An apple a day keeps the doctor away.\r\n"
|
||||
|
||||
|
||||
|
||||
class Who(protocol.Protocol):
|
||||
"""
|
||||
Return list of active users (RFC 866)
|
||||
"""
|
||||
|
||||
def connectionMade(self):
|
||||
self.transport.write(self.getUsers())
|
||||
self.transport.loseConnection()
|
||||
|
||||
|
||||
def getUsers(self):
|
||||
"""
|
||||
Return active users. Override in subclasses.
|
||||
"""
|
||||
return b"root\r\n"
|
||||
|
||||
|
||||
|
||||
class Daytime(protocol.Protocol):
|
||||
"""
|
||||
Send back the daytime in ASCII form (RFC 867).
|
||||
"""
|
||||
|
||||
def connectionMade(self):
|
||||
self.transport.write(time.asctime(time.gmtime(time.time())) + b'\r\n')
|
||||
self.transport.loseConnection()
|
||||
|
||||
|
||||
|
||||
class Time(protocol.Protocol):
|
||||
"""
|
||||
Send back the time in machine readable form (RFC 868).
|
||||
"""
|
||||
|
||||
def connectionMade(self):
|
||||
# is this correct only for 32-bit machines?
|
||||
result = struct.pack("!i", int(time.time()))
|
||||
self.transport.write(result)
|
||||
self.transport.loseConnection()
|
||||
|
||||
|
||||
__all__ = ["Echo", "Discard", "Chargen", "QOTD", "Who", "Daytime", "Time"]
|
||||
Reference in New Issue
Block a user