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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
# Copyright (c) str4d <str4d@mail.i2p>
# See COPYING for details.
from builtins import str
from builtins import object
import functools
from ometa.grammar import OMeta
from ometa.protocol import ParserProtocol
import re
import time
from twisted.internet import reactor
from twisted.internet.interfaces import IProtocolFactory
from twisted.internet.protocol import ClientFactory
from twisted.python.failure import Failure
from zope.interface import implementer
from txi2p import grammar
from txi2p.address import (
I2PAddress,
I2PServerTunnelProtocol,
I2PTunnelTransport,
)
from txi2p.sam import constants as c
KEEPALIVE_TIMEOUT = 2 * 60
def cmpSAM(a, b):
def normalize(v):
return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
a_n = normalize(a)
b_n = normalize(b)
return (a_n > b_n) - (a_n < b_n)
def peerSAM(data):
peerInfo = data.decode('utf-8').split('\n')[0].split(' ')
peerOptions = {x: y for x, y in [x.split('=', 1) for x in peerInfo[1:] if x]}
fromPort = peerOptions['FROM_PORT'] if 'FROM_PORT' in peerOptions else None
return I2PAddress(peerInfo[0], port=fromPort)
class SAMParserProtocol(ParserProtocol):
def __init__(self, *args):
ParserProtocol.__init__(self, *args)
def dataReceived(self, data):
"""
Receive and parse some data.
:param data: A ``bytes`` from Twisted.
"""
if self._disconnecting:
return
if self.receiver.currentRule == 'State_readData':
# Shortcut for efficiency
self.receiver.dataReceived(data)
else:
# Duplicated from Parsley because it expects a str but Twisted
# provides a bytes.
try:
self._parser.receive(data.decode('utf-8'))
except Exception:
self.connectionLost(Failure())
self.transport.abortConnection()
return
def makeSAMProtocol(senderFactory, receiverFactory):
g = OMeta(grammar.samGrammarSource).parseGrammar('Grammar')
return functools.partial(
SAMParserProtocol, g, senderFactory, receiverFactory, {})
class SAMSender(object):
def __init__(self, transport):
self.transport = transport
def sendHello(self):
self.transport.write(b'HELLO VERSION MIN=3.0 MAX=3.2\n')
def sendNamingLookup(self, name):
msg = 'NAMING LOOKUP NAME=%s\n' % name
self.transport.write(msg.encode('utf-8'))
def sendPing(self, data):
if data:
self.transport.write(('PING %s\n' % data).encode('utf-8'))
else:
self.transport.write(b'PING\n')
def sendPong(self, data):
if data:
self.transport.write(('PONG %s\n' % data).encode('utf-8'))
else:
self.transport.write(b'PONG\n')
class SAMReceiver(object):
wrappedProto = None
currentRule = 'State_hello'
pinger = None
lastPing = ''
pingTimeout = None
def __init__(self, sender):
self.sender = sender
def prepareParsing(self, parser):
# Store the factory for later use
self.factory = parser.factory
self.sender.sendHello()
def wrapProto(self, proto, peerAddress, invertTLS=False):
self.wrappedProto = proto
if hasattr(self.factory, 'localPort'):
localAddress = I2PAddress(self.factory.session.address,
port=self.factory.localPort)
else:
localAddress = self.factory.session.address
self.transportWrapper = I2PTunnelTransport(
self.sender.transport,
localAddress, peerAddress,
invertTLS)
proto.makeConnection(self.transportWrapper)
def dataReceived(self, data):
self.wrappedProto.dataReceived(data)
def finishParsing(self, reason):
if self.wrappedProto:
self.wrappedProto.connectionLost(reason)
else:
self.factory.connectionFailed(reason)
if hasattr(self.factory, 'session'):
self.factory.session.removeStream(self)
def hello(self, result, version=None, message=None):
if result != c.RESULT_OK:
self.factory.resultNotOK(result, message)
return
self.factory.samVersion = version
self.command()
def lookupReply(self, result, name, value=None, message=None):
if result != c.RESULT_OK:
self.factory.resultNotOK(result, message)
return
self.postLookup(value)
def _sendPing(self):
self.lastPing = str(time.time())
self.sender.sendPing(self.lastPing)
self.pingTimeout = reactor.callLater(KEEPALIVE_TIMEOUT, self.sender.transport.loseConnection)
def _resetPingTimeout(self):
if self.pingTimeout:
self.pingTimeout.cancel()
self.pinger = reactor.callLater(KEEPALIVE_TIMEOUT, self._sendPing)
def ping(self, data):
self.sender.sendPong(data)
self._resetPingTimeout()
def pong(self, data):
if (data == str(self.lastPing)):
self._resetPingTimeout()
def startPinging(self):
self.pinger = reactor.callLater(KEEPALIVE_TIMEOUT, self._sendPing)
self.currentRule = 'State_keepalive'
def stopPinging(self):
if self.pinger and self.pinger.active():
self.pinger.cancel()
if self.pingTimeout and self.pingTimeout.active():
self.pingTimeout.cancel()
class SAMFactory(ClientFactory):
currentCandidate = None
canceled = False
def _cancel(self, d):
self.currentCandidate.sender.transport.abortConnection()
self.canceled = True
def buildProtocol(self, addr):
proto = self.protocol()
proto.factory = self
self.currentCandidate = proto
return proto
def connectionFailed(self, reason):
if not self.canceled and not self.deferred.called:
self.deferred.errback(reason)
# This method is not called if an endpoint deferred errbacks
def clientConnectionFailed(self, connector, reason):
self.connectionFailed(reason)
def resultNotOK(self, result, message):
raise c.samErrorMap.get(result)(string=(message if message else result))
class SAMI2PServerTunnelProtocol(I2PServerTunnelProtocol):
def setPeer(self, data):
self.peer = peerSAM(data)
self.transport.peerAddr = self.peer
@implementer(IProtocolFactory)
class I2PFactoryWrapper(object):
protocol = SAMI2PServerTunnelProtocol
def __init__(self, wrappedFactory, serverAddr):
self.w = wrappedFactory
self.serverAddr = serverAddr
def buildProtocol(self, addr):
wrappedProto = self.w.buildProtocol(addr)
proto = self.protocol(wrappedProto, self.serverAddr)
proto.factory = self
return proto
def __getattr__(self, attr):
return getattr(self.w, attr)
|