File: endpoints.py

package info (click to toggle)
python-txi2p-tahoe 0.3.7-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 448 kB
  • sloc: python: 3,757; makefile: 163; sh: 3
file content (224 lines) | stat: -rw-r--r-- 9,492 bytes parent folder | download | duplicates (3)
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