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
|
# Copyright (c) str4d <str4d@mail.i2p>
# See COPYING for details.
from builtins import object
from twisted.internet import defer, error, interfaces
from twisted.internet.endpoints import serverFromString
from zope.interface import implementer
from txi2p.sam.base import I2PFactoryWrapper
from txi2p.sam.session import SAMSession, getSession
from txi2p.sam.stream import (
StreamConnectFactory,
StreamAcceptPort,
StreamForwardFactory,
StreamForwardPort,
)
def _parseHost(host):
# TODO: Validate I2P domain, B32 etc.
return (host, None) if host[-4:] == '.i2p' else (None, host)
@implementer(interfaces.IStreamClientEndpoint)
class SAMI2PStreamClientEndpoint(object):
"""I2P stream client endpoint backed by the SAM API.
Args:
session (txi2p.sam.SAMSession): The SAM session to connect with.
host (str): The I2P hostname or Destination to connect to.
port (int): The port to connect to inside I2P. If unset or `None`, the
default (null) port is used. Ignored if the SAM server doesn't
support SAM v3.2 or higher.
localPort (int): The port to connect from inside I2P. This can be used
to distinguish between multiple connections to the same server. If
unset or `None`, the default (null) port is used. Ignored if the SAM
server doesn't support SAM v3.2 or higher.
"""
@classmethod
def new(cls, samEndpoint, host, port=None, nickname=None, autoClose=False, keyfile=None, localPort=None, options=None, sigType=None):
"""Create an I2P client endpoint backed by the SAM API.
If a SAM session for ``nickname`` already exists, it will be used, and
all options other than ``host`` and ``port`` will be ignored. Otherwise,
a new SAM session will be created. The implication of this is that by
default, all endpoints (both client and server) created by the same
process will use the same SAM session.
Args:
samEndpoint (twisted.internet.interfaces.IStreamClientEndpoint): An
endpoint that will connect to the SAM API.
host (str): The I2P hostname or Destination to connect to.
port (int): The port to connect to inside I2P. If unset or `None`,
the default (null) port is used. Ignored if the SAM server
doesn't support SAM v3.2 or higher.
nickname (str): The SAM session nickname.
autoClose (bool): `true` if the session should close automatically
once no more connections are using it.
keyfile (str): Path to a local file containing the keypair to use
for the session Destination. If non-existent, new keys will be
generated and stored.
localPort (int): The port to connect from inside I2P. This can be
used to distinguish between multiple connections to the same
server. If unset or `None`, the default (null) port is used.
Ignored if the SAM server doesn't support SAM v3.2 or higher.
options (dict): I2CP options to configure the session with.
sigType (str): The SigType to use if generating a new Destination.
Defaults to Ed25519 if supported, falling back to
ECDSA_SHA256_P256 and then DSA_SHA1.
"""
d = getSession(nickname,
samEndpoint=samEndpoint,
autoClose=autoClose,
keyfile=keyfile,
options=options,
sigType=sigType)
return cls(d, host, port, localPort)
def __init__(self, session, host, port=None, localPort=None):
self._host, self._dest = _parseHost(host)
self._port = port
self._localPort = localPort
if isinstance(session, SAMSession):
self._session = session
else:
self._session = None
self._sessionDeferred = session
def connect(self, fac):
"""Connect over I2P.
The provided factory will have its ``buildProtocol`` method called once
an I2P client tunnel has been successfully created.
If the factory's ``buildProtocol`` returns ``None``, the connection
will immediately close.
"""
def createStream(val):
if self._session.style != 'STREAM':
raise error.UnsupportedSocketType()
i2pFac = StreamConnectFactory(fac, self._session, self._host, self._dest, self._port, self._localPort)
d = self._session.samEndpoint.connect(i2pFac)
# Once the SAM IProtocol is returned, wait for the
# real IProtocol to be returned after tunnel creation,
# and pass it to any further registered callbacks.
d.addCallback(lambda proto: i2pFac.deferred)
return d
if self._session:
return createStream(None)
def saveSession(session):
self._session = session
return None
self._sessionDeferred.addCallback(saveSession)
self._sessionDeferred.addCallback(createStream)
return self._sessionDeferred
@implementer(interfaces.IStreamServerEndpoint)
class SAMI2PStreamServerEndpoint(object):
"""I2P server endpoint backed by the SAM API.
Args:
session (txi2p.sam.SAMSession): The SAM session to listen on.
"""
@classmethod
def new(cls, samEndpoint, keyfile, port=None, nickname=None, autoClose=False, options=None, sigType=None):
"""Create an I2P server endpoint backed by the SAM API.
If a SAM session for ``nickname`` already exists, it will be used, and
all options other than ``port`` will be ignored. Otherwise, a new SAM
session will be created. The implication of this is that by default, all
endpoints (both client and server) created by the same process will use
the same SAM session.
Args:
samEndpoint (twisted.internet.interfaces.IStreamClientEndpoint): An
endpoint that will connect to the SAM API.
keyfile (str): Path to a local file containing the keypair to use
for the session Destination. If non-existent, new keys will be
generated and stored.
port (int): The port to listen on inside I2P. If unset or `None`,
the default (null) port is used. Ignored if the SAM server
doesn't support SAM v3.2 or higher.
nickname (str): The SAM session nickname.
autoClose (bool): `true` if the session should close automatically
once no more connections are using it.
options (dict): I2CP options to configure the session with.
sigType (str): The SigType to use if generating a new Destination.
Defaults to Ed25519 if supported, falling back to
ECDSA_SHA256_P256 and then DSA_SHA1.
"""
d = getSession(nickname,
samEndpoint=samEndpoint,
autoClose=autoClose,
keyfile=keyfile,
localPort=port,
options=options,
sigType=sigType)
return cls(d)
def __init__(self, session):
if isinstance(session, SAMSession):
self._session = session
else:
self._session = None
self._sessionDeferred = session
def listen(self, fac):
"""Listen over I2P.
The provided factory will have its ``buildProtocol`` method called once
an I2P server tunnel has been successfully created.
If the factory's ``buildProtocol`` returns ``None``, the connection
will immediately close.
"""
def createAcceptingStream(val):
if self._session.style != 'STREAM':
raise error.UnsupportedSocketType()
p = StreamAcceptPort(self._session, fac)
p.startListening()
return p
# def createForwardingStream(val):
# if self._session.style != 'STREAM':
# raise error.UnsupportedSocketType()
#
# serverEndpoint = serverFromString(self._reactor,
# 'tcp:0:interface=127.0.0.1')
# wrappedFactory = I2PFactoryWrapper(fac, self._session.address)
# d = serverEndpoint.listen(wrappedFactory)
#
# def setupForward(port):
# local_port = port.getHost().port
# i2pFac = StreamForwardFactory(self._session, local_port)
# d2 = self._session.samEndpoint.connect(i2pFac)
# d2.addCallback(lambda proto: i2pFac.deferred)
# d2.addCallback(lambda forwardingProto: (port, forwardingProto))
# return d2
#
# def handlePort((port, forwardingProto)):
# return StreamForwardPort(port, forwardingProto, self._session.address)
#
# d.addCallback(setupForward)
# d.addCallback(handlePort)
# return d
if self._session:
return createAcceptingStream(None)
def saveSession(session):
self._session = session
return None
self._sessionDeferred.addCallback(saveSession)
self._sessionDeferred.addCallback(createAcceptingStream)
return self._sessionDeferred
|