This repository has been archived on 2024-05-09. You can view files and clone it, but cannot push or open issues/pull-requests.
ipodderx-core/BitTorrent/RawServer_twisted.py

622 lines
20 KiB
Python

# The contents of this file are subject to the BitTorrent Open Source License
# Version 1.1 (the License). You may not copy or use this file, in either
# source code or executable form, except in compliance with the License. You
# may obtain a copy of the License at http://www.bittorrent.com/license/.
#
# Software distributed under the License is distributed on an AS IS basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
# Written by Greg Hazel
import os
import sys
import socket
import signal
import struct
import thread
from cStringIO import StringIO
from traceback import print_exc, print_stack
from BitTorrent import BTFailure, WARNING, CRITICAL, FAQ_URL
noSignals = True
if os.name == 'nt':
try:
from twisted.internet import iocpreactor
iocpreactor.proactor.install()
noSignals = False
except:
# just as limited (if not more) as select, and also (supposedly) buggy
#try:
# from twisted.internet import win32eventreactor
# win32eventreactor.install()
#except:
# pass
pass
else:
try:
from twisted.internet import kqreactor
kqreactor.install()
except:
try:
from twisted.internet import pollreactor
pollreactor.install()
except:
pass
#the default reactor is select-based, and will be install()ed if another has not
from twisted.internet import reactor, task, error
import twisted.copyright
if int(twisted.copyright.version.split('.')[0]) < 2:
raise ImportError("RawServer_twisted requires twisted 2.0.0 or greater")
from twisted.internet.protocol import DatagramProtocol, Protocol, Factory, ClientFactory
from twisted.protocols.policies import TimeoutMixin
NOLINGER = struct.pack('ii', 1, 0)
class Handler(object):
# there is only a semantic difference between "made" and "started".
# I prefer "started"
def connection_started(self, s):
self.connection_made(s)
def connection_made(self, s):
pass
def connection_lost(self, s):
pass
# Maybe connection_lost should just have a default 'None' exception parameter
def connection_failed(self, addr, exception):
pass
def connection_flushed(self, s):
pass
def data_came_in(self, addr, datagram):
pass
class ConnectionWrapper(object):
def __init__(self, rawserver, handler, context, tos=0):
self.dying = 0
self.ip = None
self.port = None
self.transport = None
self.reset_timeout = None
self.post_init(rawserver, handler, context)
self.tos = tos
self.buffer = OutputBuffer(self)
def post_init(self, rawserver, handler, context):
self.rawserver = rawserver
self.handler = handler
self.context = context
if self.rawserver:
self.rawserver.single_sockets[self] = self
def get_socket(self):
s = None
try:
s = self.transport.getHandle()
except:
try:
# iocpreactor doesn't implement ISystemHandle like it should
s = self.transport.socket
except:
pass
return s
def attach_transport(self, transport, reset_timeout):
self.transport = transport
self.reset_timeout = reset_timeout
try:
address = self.transport.getPeer()
except:
try:
# udp, for example
address = self.transport.getHost()
except:
if not self.transport.__dict__.has_key("state"):
self.transport.state = "NO STATE!"
sys.stderr.write("UNKNOWN HOST/PEER: " + str(self.transport) + ":" + str(self.transport.state)+ ":" + str(self.handler) + "\n")
print_stack()
# fallback incase the unknown happens,
# there's no use raising an exception
address = ("unknown", -1)
pass
try:
self.ip = address.host
self.port = address.port
except:
#unix sockets, for example
pass
if self.tos != 0:
s = self.get_socket()
try:
s.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, self.tos)
except:
pass
def sendto(self, packet, flags, addr):
# all this can go away once we pin down the bug
if not hasattr(self.transport, "listening"):
self.rawserver.errorfunc(WARNING, "UDP port never setup properly when asked to write")
elif not self.transport.listening:
self.rawserver.errorfunc(WARNING, "UDP port cleaned up already when asked to write")
ret = None
try:
ret = self.transport.write(packet, addr)
except Exception, e:
self.rawserver.errorfunc(WARNING, "UDP sendto failed: %s" % str(e))
return ret
def write(self, b):
self.buffer.add(b)
def _flushed(self):
s = self
#why do you tease me so?
if s.handler is not None:
#calling flushed from the write is bad form
self.rawserver.add_task(s.handler.connection_flushed, 0, (s,))
def is_flushed(self):
return self.buffer.is_flushed()
def shutdown(self, how):
if how == socket.SHUT_WR:
self.transport.loseWriteConnection()
self.buffer.stopWriting()
elif how == socket.SHUT_RD:
self.transport.stopListening()
else:
self.close()
def close(self):
self.buffer.stopWriting()
# opt for no "connection_lost" callback since we know that
self.dying = 1
if self.rawserver.config['close_with_rst']:
try:
s = self.get_socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, NOLINGER)
except:
pass
if self.rawserver.udp_sockets.has_key(self):
# udp connections should only call stopListening
self.transport.stopListening()
else:
self.transport.loseConnection()
class OutputBuffer(object):
def __init__(self, connection):
self.connection = connection
self.consumer = None
self.buffer = StringIO()
def is_flushed(self):
return (self.buffer.tell() == 0)
def add(self, b):
# sometimes we get strings, sometimes we get buffers. ugg.
if (isinstance(b, buffer)):
b = str(b)
self.buffer.write(b)
if self.consumer is None:
self.beginWriting()
def beginWriting(self):
self.stopWriting()
self.consumer = self.connection.transport
self.consumer.registerProducer(self, False)
def stopWriting(self):
if self.consumer is not None:
self.consumer.unregisterProducer()
self.consumer = None
def resumeProducing(self):
if self.consumer is not None:
if self.buffer.tell() > 0:
self.consumer.write(self.buffer.getvalue())
self.buffer.seek(0)
self.buffer.truncate(0)
self.connection._flushed()
else:
self.stopWriting()
def pauseProducing(self):
pass
def stopProducing(self):
pass
class CallbackConnection(object):
def attachTransport(self, transport, connection, *args):
s = connection
if s is None:
s = ConnectionWrapper(*args)
s.attach_transport(transport, self.optionalResetTimeout)
self.connection = s
def connectionMade(self):
s = self.connection
s.handler.connection_started(s)
self.optionalResetTimeout()
def connectionLost(self, reason):
reactor.callLater(0, self.post_connectionLost, reason)
#twisted api inconsistancy workaround
#sometimes connectionLost is called (not fired) from inside write()
def post_connectionLost(self, reason):
#hack to try and dig up the connection if one was ever made
if not self.__dict__.has_key("connection"):
self.connection = self.factory.connection
if self.connection is not None:
self.factory.rawserver._remove_socket(self.connection)
def dataReceived(self, data):
self.optionalResetTimeout()
s = self.connection
s.rawserver._make_wrapped_call(s.handler.data_came_in,
(s, data), s)
def datagramReceived(self, data, (host, port)):
s = self.connection
s.rawserver._make_wrapped_call(s.handler.data_came_in,
((host, port), data), s)
def connectionRefused(self):
s = self.connection
dns = (s.ip, s.port)
reason = "connection refused"
if not s.dying:
# this might not work - reason is not an exception
s.handler.connection_failed(dns, reason)
#so we don't get failed then closed
s.dying = 1
s.rawserver._remove_socket(s)
def optionalResetTimeout(self):
if self.can_timeout:
self.resetTimeout()
class CallbackProtocol(CallbackConnection, TimeoutMixin, Protocol):
def makeConnection(self, transport):
self.can_timeout = 1
self.setTimeout(self.factory.rawserver.config['socket_timeout'])
self.attachTransport(transport, self.factory.connection, *self.factory.connection_args)
Protocol.makeConnection(self, transport)
class CallbackDatagramProtocol(CallbackConnection, DatagramProtocol):
def startProtocol(self):
self.can_timeout = 0
self.attachTransport(self.transport, self.connection, ())
DatagramProtocol.startProtocol(self)
def connectionRefused(self):
# we don't use these at all for udp, so skip the CallbackConnection
DatagramProtocol.connectionRefused(self)
class OutgoingConnectionFactory(ClientFactory):
def clientConnectionFailed(self, connector, reason):
peer = connector.getDestination()
dns = (peer.host, peer.port)
# opt-out
if not self.connection.dying:
# this might not work - reason is not an exception
self.connection.handler.connection_failed(dns, reason)
#so we don't get failed then closed
self.connection.dying = 1
self.rawserver._remove_socket(self.connection)
def UnimplementedWarning(msg):
#ok, I think people get the message
#print "UnimplementedWarning: " + str(msg)
pass
#Encoder calls stop_listening(socket) then socket.close()
#to us they mean the same thing, so swallow the second call
class CloseSwallower:
def close(self):
pass
#storage for socket creation requestions, and proxy once the connection is made
class SocketProxy(object):
def __init__(self, port, bind, reuse, tos, protocol):
self.port = port
self.bind = bind
self.reuse = reuse
self.tos = tos
self.protocol = protocol
self.connection = None
def __getattr__(self, name):
try:
return getattr(self.connection, name)
except:
raise AttributeError, name
def default_error_handler(level, message):
print message
class RawServerMixin(object):
def __init__(self, doneflag, config, noisy=True,
errorfunc=default_error_handler, tos=0):
self.doneflag = doneflag
self.noisy = noisy
self.errorfunc = errorfunc
self.config = config
self.tos = tos
self.ident = thread.get_ident()
def _make_wrapped_call(self, function, args, wrapper=None, context=None):
try:
function(*args)
except KeyboardInterrupt:
raise
except Exception, e: # hopefully nothing raises strings
# Incoming sockets can be assigned to a particular torrent during
# a data_came_in call, and it's possible (though not likely) that
# there could be a torrent-specific exception during the same call.
# Therefore read the context after the call.
if wrapper is not None:
context = wrapper.context
if self.noisy and context is None:
data = StringIO()
print_exc(file=data)
data.seek(-1)
self.errorfunc(CRITICAL, data.read())
if context is not None:
context.got_exception(e)
# must be called from the main thread
def install_sigint_handler(self):
signal.signal(signal.SIGINT, self._handler)
def _handler(self, signum, frame):
self.external_add_task(self.doneflag.set, 0)
# Allow pressing ctrl-c multiple times to raise KeyboardInterrupt,
# in case the program is in an infinite loop
signal.signal(signal.SIGINT, signal.default_int_handler)
class RawServer(RawServerMixin):
def __init__(self, doneflag, config, noisy=True,
errorfunc=default_error_handler, tos=0):
RawServerMixin.__init__(self, doneflag, config, noisy, errorfunc, tos)
self.listening_handlers = {}
self.single_sockets = {}
self.udp_sockets = {}
self.live_contexts = {None : 1}
self.listened = 0
def add_context(self, context):
self.live_contexts[context] = 1
def remove_context(self, context):
del self.live_contexts[context]
def autoprune(self, f, *a, **kw):
if self.live_contexts.has_key(kw['context']):
f(*a, **kw)
def add_task(self, func, delay, args=(), context=None):
assert thread.get_ident() == self.ident
assert type(args) == list or type(args) == tuple
#we're going to check again later anyway
#if self.live_contexts.has_key(context):
reactor.callLater(delay, self.autoprune, self._make_wrapped_call,
func, args, context=context)
def external_add_task(self, func, delay, args=(), context=None):
assert type(args) == list or type(args) == tuple
reactor.callFromThread(self.add_task, func, delay, args, context)
def create_unixserversocket(filename):
s = SocketProxy(0, filename, True, 0, 'tcp')
s.factory = Factory()
if s.reuse == False:
UnimplementedWarning("You asked for reuse to be off when binding. Sorry, I can't do that.")
listening_port = reactor.listenUNIX(s.bind, s.factory)
listening_port.listening = 1
s.listening_port = listening_port
return s
create_unixserversocket = staticmethod(create_unixserversocket)
def create_serversocket(port, bind='', reuse=False, tos=0):
s = SocketProxy(port, bind, reuse, tos, 'tcp')
s.factory = Factory()
if s.reuse == False:
UnimplementedWarning("You asked for reuse to be off when binding. Sorry, I can't do that.")
try:
listening_port = reactor.listenTCP(s.port, s.factory, interface=s.bind)
except error.CannotListenError, e:
if e[0] != 0:
raise e.socketError
else:
raise
listening_port.listening = 1
s.listening_port = listening_port
return s
create_serversocket = staticmethod(create_serversocket)
def create_udpsocket(port, bind='', reuse=False, tos=0):
s = SocketProxy(port, bind, reuse, tos, 'udp')
s.protocol = CallbackDatagramProtocol()
c = ConnectionWrapper(None, None, None, tos)
s.connection = c
s.protocol.connection = c
if s.reuse == False:
UnimplementedWarning("You asked for reuse to be off when binding. Sorry, I can't do that.")
try:
listening_port = reactor.listenUDP(s.port, s.protocol, interface=s.bind)
except error.CannotListenError, e:
raise e.socketError
listening_port.listening = 1
s.listening_port = listening_port
return s
create_udpsocket = staticmethod(create_udpsocket)
def start_listening(self, serversocket, handler, context=None):
s = serversocket
s.factory.rawserver = self
s.factory.protocol = CallbackProtocol
s.factory.connection = None
s.factory.connection_args = (self, handler, context, serversocket.tos)
if not s.listening_port.listening:
s.listening_port.startListening()
s.listening_port.listening = 1
self.listening_handlers[s] = s.listening_port
#provides a harmless close() method
s.connection = CloseSwallower()
def start_listening_udp(self, serversocket, handler, context=None):
s = serversocket
c = s.connection
c.post_init(self, handler, context)
if not s.listening_port.listening:
s.listening_port.startListening()
s.listening_port.listening = 1
self.listening_handlers[serversocket] = s.listening_port
self.udp_sockets[c] = c
def stop_listening(self, serversocket):
listening_port = self.listening_handlers[serversocket]
try:
listening_port.stopListening()
listening_port.listening = 0
except error.NotListeningError:
pass
del self.listening_handlers[serversocket]
def stop_listening_udp(self, serversocket):
listening_port = self.listening_handlers[serversocket]
listening_port.stopListening()
del self.listening_handlers[serversocket]
del self.udp_sockets[serversocket.connection]
del self.single_sockets[serversocket.connection]
def start_connection(self, dns, handler, context=None, do_bind=True):
addr = dns[0]
port = int(dns[1])
bindaddr = None
if do_bind:
bindaddr = self.config['bind']
if bindaddr and len(bindaddr) >= 0:
bindaddr = (bindaddr, 0)
else:
bindaddr = None
factory = OutgoingConnectionFactory()
factory.protocol = CallbackProtocol
factory.rawserver = self
c = ConnectionWrapper(self, handler, context, self.tos)
factory.connection = c
factory.connection_args = ()
connector = reactor.connectTCP(addr, port, factory, bindAddress=bindaddr)
self.single_sockets[c] = c
return c
def async_start_connection(self, dns, handler, context=None, do_bind=True):
self.start_connection(dns, handler, context, do_bind)
def wrap_socket(self, sock, handler, context=None, ip=None):
raise Unimplemented("wrap_socket")
def listen_forever(self):
self.ident = thread.get_ident()
if self.listened:
UnimplementedWarning("listen_forever() should only be called once per reactor.")
self.listened = 1
l = task.LoopingCall(self.stop)
l.start(1, now = False)
if noSignals:
reactor.run(installSignalHandlers=False)
else:
reactor.run()
def listen_once(self, period=1e9):
UnimplementedWarning("listen_once() Might not return until there is activity, and might not process the event you want. Use listen_forever().")
reactor.iterate(period)
def stop(self):
if (self.doneflag.isSet()):
for connection in self.single_sockets.values():
try:
#I think this still sends all the data
connection.close()
except:
pass
reactor.suggestThreadPoolSize(0)
reactor.stop()
def _remove_socket(self, s):
# opt-out
if not s.dying:
self._make_wrapped_call(s.handler.connection_lost, (s,), s)
s.handler = None
del self.single_sockets[s]