Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Sign In

meejah / txtorcon / 1164

2 Oct 2018 - 22:05 coverage increased (+0.0002%) to 99.957%
1164

Pull #316

travis-ci

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
fix asserts
Pull Request #316: Ticket313 pivate key file

21 of 21 new or added lines in 1 file covered. (100.0%)

8 existing lines in 3 files now uncovered.

4648 of 4650 relevant lines covered (99.96%)

11.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/txtorcon/socks.py
1
# in-progress; implementing SOCKS5 client-side stuff as extended by
2
# tor because txsocksx will not be getting Python3 support any time
3
# soon, and its underlying dependency (Parsely) also doesn't support
4
# Python3. Also, Tor's SOCKS5 implementation is especially simple,
5
# since it doesn't do BIND or UDP ASSOCIATE.
6

7
from __future__ import print_function
12×
8

9
import six
12×
10
import struct
12×
11
from socket import inet_pton, inet_ntoa, inet_aton, AF_INET6, AF_INET
12×
12

13
from twisted.internet.defer import inlineCallbacks, returnValue, Deferred
12×
14
from twisted.internet.protocol import Protocol, Factory
12×
15
from twisted.internet.address import IPv4Address, IPv6Address, HostnameAddress
12×
16
from twisted.python.failure import Failure
12×
17
from twisted.protocols import portforward
12×
18
from twisted.protocols import tls
12×
19
from twisted.internet.interfaces import IStreamClientEndpoint
12×
20
from zope.interface import implementer
12×
21
import ipaddress
12×
22
import automat
12×
23

24
from txtorcon import util
12×
25

26

27
__all__ = (
12×
28
    'resolve',
29
    'resolve_ptr',
30
    'SocksError',
31
    'GeneralServerFailureError',
32
    'ConnectionNotAllowedError',
33
    'NetworkUnreachableError',
34
    'HostUnreachableError',
35
    'ConnectionRefusedError',
36
    'TtlExpiredError',
37
    'CommandNotSupportedError',
38
    'AddressTypeNotSupportedError',
39
    'TorSocksEndpoint',
40
)
41

42

43
def _create_ip_address(host, port):
12×
44
    if not isinstance(host, six.text_type):
12×
45
        raise ValueError(
12×
46
            "'host' must be {}, not {}".format(six.text_type, type(host))
47
        )
48
    try:
12×
49
        a = ipaddress.ip_address(host)
12×
50
    except ValueError:
12×
51
        a = None
12×
52
    if isinstance(a, ipaddress.IPv4Address):
12×
53
        return IPv4Address('TCP', host, port)
12×
54
    if isinstance(a, ipaddress.IPv6Address):
12×
55
        return IPv6Address('TCP', host, port)
12×
56
    addr = HostnameAddress(host, port)
12×
57
    addr.host = host
12×
58
    return addr
12×
59

60

61
class _SocksMachine(object):
12×
62
    """
63
    trying to prototype the SOCKS state-machine in automat
64

65
    This is a SOCKS state machine to make a single request.
66
    """
67

68
    _machine = automat.MethodicalMachine()
12×
69
    SUCCEEDED = 0x00
12×
70
    REPLY_IPV4 = 0x01
12×
71
    REPLY_HOST = 0x03
12×
72
    REPLY_IPV6 = 0x04
12×
73

74
    # XXX address = (host, port) instead
75
    def __init__(self, req_type, host,
12×
76
                 port=0,
77
                 on_disconnect=None,
78
                 on_data=None,
79
                 create_connection=None):
80
        if req_type not in self._dispatch:
12×
81
            raise ValueError(
12×
82
                "Unknown request type '{}'".format(req_type)
83
            )
84
        if req_type == 'CONNECT' and create_connection is None:
12×
85
            raise ValueError(
12×
86
                "create_connection function required for '{}'".format(
87
                    req_type
88
                )
89
            )
90
        if not isinstance(host, (bytes, str, six.text_type)):
12×
91
            raise ValueError(
12×
92
                "'host' must be text".format(type(host))
93
            )
94
        # XXX what if addr is None?
95
        self._req_type = req_type
12×
96
        self._addr = _create_ip_address(six.text_type(host), port)
12×
97
        self._data = b''
12×
98
        self._on_disconnect = on_disconnect
12×
99
        self._create_connection = create_connection
12×
100
        # XXX FIXME do *one* of these:
101
        self._on_data = on_data
12×
102
        self._outgoing_data = []
12×
103
        # the other side of our proxy
104
        self._sender = None
12×
105
        self._when_done = util.SingleObserver()
12×
106

107
    def when_done(self):
12×
108
        """
109
        Returns a Deferred that fires when we're done
110
        """
111
        return self._when_done.when_fired()
12×
112

113
    def _data_to_send(self, data):
12×
114
        if self._on_data:
12×
115
            self._on_data(data)
12×
116
        else:
117
            self._outgoing_data.append(data)
12×
118

119
    def send_data(self, callback):
12×
120
        """
121
        drain all pending data by calling `callback()` on it
122
        """
123
        # a "for x in self._outgoing_data" would potentially be more
124
        # efficient, but then there's no good way to bubble exceptions
125
        # from callback() out without lying about how much data we
126
        # processed .. or eat the exceptions in here.
127
        while len(self._outgoing_data):
12×
128
            data = self._outgoing_data.pop(0)
12×
129
            callback(data)
12×
130

131
    def feed_data(self, data):
12×
132
        # I feel like maybe i'm doing all this buffering-stuff
133
        # wrong. but I also don't want a bunch of "received 1 byte"
134
        # etc states hanging off everything that can "get data"
135
        self._data += data
12×
136
        self.got_data()
12×
137

138
    @_machine.output()
12×
139
    def _parse_version_reply(self):
140
        "waiting for a version reply"
141
        if len(self._data) >= 2:
12×
142
            reply = self._data[:2]
12×
143
            self._data = self._data[2:]
12×
144
            (version, method) = struct.unpack('BB', reply)
12×
145
            if version == 5 and method in [0x00, 0x02]:
12×
146
                self.version_reply(method)
12×
147
            else:
148
                if version != 5:
12×
149
                    self.version_error(SocksError(
12×
150
                        "Expected version 5, got {}".format(version)))
151
                else:
152
                    self.version_error(SocksError(
12×
153
                        "Wanted method 0 or 2, got {}".format(method)))
154

155
    def _parse_ipv4_reply(self):
12×
156
        if len(self._data) >= 10:
12×
157
            addr = inet_ntoa(self._data[4:8])
12×
158
            port = struct.unpack('H', self._data[8:10])[0]
12×
159
            self._data = self._data[10:]
12×
160
            if self._req_type == 'CONNECT':
12×
161
                self.reply_ipv4(addr, port)
12×
162
            else:
163
                self.reply_domain_name(addr)
12×
164

165
    def _parse_ipv6_reply(self):
12×
166
        if len(self._data) >= 22:
12×
167
            addr = self._data[4:20]
12×
168
            port = struct.unpack('H', self._data[20:22])[0]
12×
169
            self._data = self._data[22:]
12×
170
            self.reply_ipv6(addr, port)
12×
171

172
    def _parse_domain_name_reply(self):
12×
173
        assert len(self._data) >= 8  # _parse_request_reply checks this
12×
174
        addrlen = struct.unpack('B', self._data[4:5])[0]
12×
175
        # may simply not have received enough data yet...
176
        if len(self._data) < (5 + addrlen + 2):
12×
177
            return
12×
178
        addr = self._data[5:5 + addrlen]
12×
179
        # port = struct.unpack('H', self._data[5 + addrlen:5 + addrlen + 2])[0]
180
        self._data = self._data[5 + addrlen + 2:]
12×
181
        self.reply_domain_name(addr)
12×
182

183
    @_machine.output()
12×
184
    def _parse_request_reply(self):
185
        "waiting for a reply to our request"
186
        # we need at least 6 bytes of data: 4 for the "header", such
187
        # as it is, and 2 more if it's DOMAINNAME (for the size) or 4
188
        # or 16 more if it's an IPv4/6 address reply. plus there's 2
189
        # bytes on the end for the bound port.
190
        if len(self._data) < 8:
12×
191
            return
12×
192
        msg = self._data[:4]
12×
193

194
        # not changing self._data yet, in case we've not got
195
        # enough bytes so far.
196
        (version, reply, _, typ) = struct.unpack('BBBB', msg)
12×
197

198
        if version != 5:
12×
199
            self.reply_error(SocksError(
12×
200
                "Expected version 5, got {}".format(version)))
201
            return
12×
202

203
        if reply != self.SUCCEEDED:
12×
204
            self.reply_error(_create_socks_error(reply))
12×
205
            return
12×
206

207
        reply_dispatcher = {
12×
208
            self.REPLY_IPV4: self._parse_ipv4_reply,
209
            self.REPLY_HOST: self._parse_domain_name_reply,
210
            self.REPLY_IPV6: self._parse_ipv6_reply,
211
        }
212
        try:
12×
213
            method = reply_dispatcher[typ]
12×
214
        except KeyError:
12×
215
            self.reply_error(SocksError(
12×
216
                "Unexpected response type {}".format(typ)))
217
            return
12×
218
        method()
12×
219

220
    @_machine.output()
12×
221
    def _make_connection(self, addr, port):
222
        "make our proxy connection"
223
        sender = self._create_connection(addr, port)
12×
224
        # XXX look out! we're depending on this "sender" implementing
225
        # certain Twisted APIs, and the state-machine shouldn't depend
226
        # on that.
227

228
        # XXX also, if sender implements producer/consumer stuff, we
229
        # should register ourselves (and implement it to) -- but this
230
        # should really be taking place outside the state-machine in
231
        # "the I/O-doing" stuff
232
        self._sender = sender
12×
233
        self._when_done.fire(sender)
12×
234

235
    @_machine.output()
12×
236
    def _domain_name_resolved(self, domain):
237
        self._when_done.fire(domain)
12×
238

239
    @_machine.input()
12×
240
    def connection(self):
241
        "begin the protocol (i.e. connection made)"
242

243
    @_machine.input()
12×
244
    def disconnected(self, error):
245
        "the connection has gone away"
246

247
    @_machine.input()
12×
248
    def got_data(self):
249
        "we recevied some data and buffered it"
250

251
    @_machine.input()
12×
252
    def version_reply(self, auth_method):
253
        "the SOCKS server replied with a version"
254

255
    @_machine.input()
12×
256
    def version_error(self, error):
257
        "the SOCKS server replied, but we don't understand"
258

259
    @_machine.input()
12×
260
    def reply_error(self, error):
261
        "the SOCKS server replied with an error"
262

263
    @_machine.input()
12×
264
    def reply_ipv4(self, addr, port):
265
        "the SOCKS server told me an IPv4 addr, port"
266

267
    @_machine.input()
12×
268
    def reply_ipv6(self, addr, port):
269
        "the SOCKS server told me an IPv6 addr, port"
270

271
    @_machine.input()
12×
272
    def reply_domain_name(self, domain):
273
        "the SOCKS server told me a domain-name"
274

275
    @_machine.input()
12×
276
    def answer(self):
277
        "the SOCKS server replied with an answer"
278

279
    @_machine.output()
12×
280
    def _send_version(self):
281
        "sends a SOCKS version reply"
282
        self._data_to_send(
12×
283
            # for anonymous(0) *and* authenticated (2): struct.pack('BBBB', 5, 2, 0, 2)
284
            struct.pack('BBB', 5, 1, 0)
285
        )
286

287
    @_machine.output()
12×
288
    def _disconnect(self, error):
289
        "done"
290
        if self._on_disconnect:
12×
291
            self._on_disconnect(str(error))
12×
292
        if self._sender:
12×
293
            self._sender.connectionLost(Failure(error))
12×
294
        self._when_done.fire(Failure(error))
12×
295

296
    @_machine.output()
12×
297
    def _send_request(self, auth_method):
298
        "send the request (connect, resolve or resolve_ptr)"
299
        assert auth_method == 0x00  # "no authentication required"
12×
300
        return self._dispatch[self._req_type](self)
12×
301

302
    @_machine.output()
12×
303
    def _relay_data(self):
304
        "relay any data we have"
305
        if self._data:
12×
306
            d = self._data
12×
307
            self._data = b''
12×
308
            # XXX this is "doing I/O" in the state-machine and it
309
            # really shouldn't be ... probably want a passed-in
310
            # "relay_data" callback or similar?
311
            self._sender.dataReceived(d)
12×
312

313
    def _send_connect_request(self):
12×
314
        "sends CONNECT request"
315
        # XXX needs to support v6 ... or something else does
316
        host = self._addr.host
12×
317
        port = self._addr.port
12×
318

319
        if isinstance(self._addr, (IPv4Address, IPv6Address)):
12×
320
            is_v6 = isinstance(self._addr, IPv6Address)
12×
321
            self._data_to_send(
12×
322
                struct.pack(
323
                    '!BBBB4sH',
324
                    5,                   # version
325
                    0x01,                # command
326
                    0x00,                # reserved
327
                    0x04 if is_v6 else 0x01,
328
                    inet_pton(AF_INET6 if is_v6 else AF_INET, host),
329
                    port,
330
                )
331
            )
332
        else:
333
            host = host.encode('ascii')
12×
334
            self._data_to_send(
12×
335
                struct.pack(
336
                    '!BBBBB{}sH'.format(len(host)),
337
                    5,                   # version
338
                    0x01,                # command
339
                    0x00,                # reserved
340
                    0x03,
341
                    len(host),
342
                    host,
343
                    port,
344
                )
345
            )
346

347
    @_machine.output()
12×
348
    def _send_resolve_request(self):
349
        "sends RESOLVE_PTR request (Tor custom)"
350
        host = self._addr.host.encode()
12×
351
        self._data_to_send(
12×
352
            struct.pack(
353
                '!BBBBB{}sH'.format(len(host)),
354
                5,                   # version
355
                0xF0,                # command
356
                0x00,                # reserved
357
                0x03,                # DOMAINNAME
358
                len(host),
359
                host,
360
                0,  # self._addr.port?
361
            )
362
        )
363

364
    @_machine.output()
12×
365
    def _send_resolve_ptr_request(self):
366
        "sends RESOLVE_PTR request (Tor custom)"
367
        addr_type = 0x04 if isinstance(self._addr, ipaddress.IPv4Address) else 0x01
12×
368
        encoded_host = inet_aton(self._addr.host)
12×
369
        self._data_to_send(
12×
370
            struct.pack(
371
                '!BBBB4sH',
372
                5,                   # version
373
                0xF1,                # command
374
                0x00,                # reserved
375
                addr_type,
376
                encoded_host,
377
                0,                   # port; unused? SOCKS is fun
378
            )
379
        )
380

381
    @_machine.state(initial=True)
12×
382
    def unconnected(self):
383
        "not yet connected"
384

385
    @_machine.state()
12×
386
    def sent_version(self):
387
        "we've sent our version request"
388

389
    @_machine.state()
12×
390
    def sent_request(self):
391
        "we've sent our stream/etc request"
392

393
    @_machine.state()
12×
394
    def relaying(self):
395
        "received our response, now we can relay"
396

397
    @_machine.state()
12×
398
    def abort(self, error_message):
399
        "we've encountered an error"
400

401
    @_machine.state()
12×
402
    def done(self):
403
        "operations complete"
404

405
    unconnected.upon(
12×
406
        connection,
407
        enter=sent_version,
408
        outputs=[_send_version],
409
    )
410

411
    sent_version.upon(
12×
412
        got_data,
413
        enter=sent_version,
414
        outputs=[_parse_version_reply],
415
    )
416
    sent_version.upon(
12×
417
        version_error,
418
        enter=abort,
419
        outputs=[_disconnect],
420
    )
421
    sent_version.upon(
12×
422
        version_reply,
423
        enter=sent_request,
424
        outputs=[_send_request],
425
    )
426
    sent_version.upon(
12×
427
        disconnected,
428
        enter=unconnected,
429
        outputs=[_disconnect]
430
    )
431

432
    sent_request.upon(
12×
433
        got_data,
434
        enter=sent_request,
435
        outputs=[_parse_request_reply],
436
    )
437
    sent_request.upon(
12×
438
        reply_ipv4,
439
        enter=relaying,
440
        outputs=[_make_connection],
441
    )
442
    sent_request.upon(
12×
443
        reply_ipv6,
444
        enter=relaying,
445
        outputs=[_make_connection],
446
    )
447
    # XXX this isn't always a _domain_name_resolved -- if we're a
448
    # req_type CONNECT then it's _make_connection_domain ...
449
    sent_request.upon(
12×
450
        reply_domain_name,
451
        enter=done,
452
        outputs=[_domain_name_resolved],
453
    )
454
    sent_request.upon(
12×
455
        reply_error,
456
        enter=abort,
457
        outputs=[_disconnect],
458
    )
459
# XXX FIXME this needs a test
460
    sent_request.upon(
12×
461
        disconnected,
462
        enter=abort,
463
        outputs=[_disconnect],  # ... or is this redundant?
464
    )
465

466
    relaying.upon(
12×
467
        got_data,
468
        enter=relaying,
469
        outputs=[_relay_data],
470
    )
471
    relaying.upon(
12×
472
        disconnected,
473
        enter=done,
474
        outputs=[_disconnect],
475
    )
476

477
    abort.upon(
12×
478
        got_data,
479
        enter=abort,
480
        outputs=[],
481
    )
482
    abort.upon(
12×
483
        disconnected,
484
        enter=abort,
485
        outputs=[],
486
    )
487

488
    done.upon(
12×
489
        disconnected,
490
        enter=done,
491
        outputs=[],
492
    )
493

494
    _dispatch = {
12×
495
        'CONNECT': _send_connect_request,
496
        'RESOLVE': _send_resolve_request,
497
        'RESOLVE_PTR': _send_resolve_ptr_request,
498
    }
499

500

501
class _TorSocksProtocol(Protocol):
12×
502

503
    def __init__(self, host, port, socks_method, factory):
12×
504
        self._machine = _SocksMachine(
12×
505
            req_type=socks_method,
506
            host=host,  # noqa unicode() on py3, py2? we want idna, actually?
507
            port=port,
508
            on_disconnect=self._on_disconnect,
509
            on_data=self._on_data,
510
            create_connection=self._create_connection,
511
        )
512
        self._factory = factory
12×
513

514
    def when_done(self):
12×
515
        return self._machine.when_done()
12×
516

517
    def connectionMade(self):
12×
518
        self._machine.connection()
12×
519
        # we notify via the factory that we have teh
520
        # locally-connecting host -- this is e.g. used by the "stream
521
        # over one particular circuit" code to determine the local
522
        # port that "our" SOCKS connection went to
523
        self.factory._did_connect(self.transport.getHost())
12×
524

525
    def connectionLost(self, reason):
12×
526
        self._machine.disconnected(SocksError(reason))
12×
527

528
    def dataReceived(self, data):
12×
529
        self._machine.feed_data(data)
12×
530

531
    def _on_data(self, data):
12×
532
        self.transport.write(data)
12×
533

534
    def _create_connection(self, addr, port):
12×
535
        addr = IPv4Address('TCP', addr, port)
12×
536
        sender = self._factory.buildProtocol(addr)
12×
537
        client_proxy = portforward.ProxyClient()
12×
538
        sender.makeConnection(self.transport)
12×
539
        # portforward.ProxyClient is going to call setPeer but this
540
        # probably doesn't have it...
541
        setattr(sender, 'setPeer', lambda _: None)
12×
542
        client_proxy.setPeer(sender)
12×
543
        self._sender = sender
12×
544
        return sender
12×
545

546
    def _on_disconnect(self, error_message):
12×
547
        self.transport.loseConnection()
12×
548
        # self.transport.abortConnection()#SocksError(error_message)) ?
549

550

551
class _TorSocksFactory(Factory):
12×
552
    protocol = _TorSocksProtocol
12×
553

554
    # XXX should do validation on this stuff so we get errors before
555
    # building the protocol
556
    def __init__(self, *args, **kw):
12×
557
        self._args = args
12×
558
        self._kw = kw
12×
559
        self._host = None
12×
560
        self._when_connected = util.SingleObserver()
12×
561

562
    def _get_address(self):
12×
563
        """
564
        Returns a Deferred that fires with the transport's getHost()
565
        when this SOCKS protocol becomes connected.
566
        """
567
        return self._when_connected.when_fired()
12×
568

569
    def _did_connect(self, host):
12×
570
        self._host = host
12×
571
        self._when_connected.fire(host)
12×
572

573
    def buildProtocol(self, addr):
12×
574
        p = self.protocol(*self._args, **self._kw)
12×
575
        p.factory = self
12×
576
        return p
12×
577

578

579
class SocksError(Exception):
12×
580
    code = None
12×
581
    message = ''
12×
582

583
    def __init__(self, message='', code=None):
12×
584
        super(SocksError, self).__init__(message or self.message)
12×
585
        self.message = message or self.message
12×
586
        self.code = code or self.code
12×
587

588

589
class GeneralServerFailureError(SocksError):
12×
590
    code = 0x01
12×
591
    message = 'general SOCKS server failure'
12×
592

593

594
class ConnectionNotAllowedError(SocksError):
12×
595
    code = 0x02
12×
596
    message = 'connection not allowed by ruleset'
12×
597

598

599
class NetworkUnreachableError(SocksError):
12×
600
    code = 0x03
12×
601
    message = 'Network unreachable'
12×
602

603

604
class HostUnreachableError(SocksError):
12×
605
    code = 0x04
12×
606
    message = 'Host unreachable'
12×
607

608

609
class ConnectionRefusedError(SocksError):
12×
610
    code = 0x05
12×
611
    message = 'Connection refused'
12×
612

613

614
class TtlExpiredError(SocksError):
12×
615
    code = 0x06
12×
616
    message = 'TTL expired'
12×
617

618

619
class CommandNotSupportedError(SocksError):
12×
620
    code = 0x07
12×
621
    message = 'Command not supported'
12×
622

623

624
class AddressTypeNotSupportedError(SocksError):
12×
625
    code = 0x08
12×
626
    message = 'Address type not supported'
12×
627

628

629
_socks_errors = {cls.code: cls for cls in SocksError.__subclasses__()}
12×
630

631

632
def _create_socks_error(code):
12×
633
    try:
12×
634
        return _socks_errors[code]()
12×
635
    except KeyError:
12×
636
        return SocksError("Unknown SOCKS error-code {}".format(code),
12×
637
                          code=code)
638

639

640
@inlineCallbacks
12×
641
def resolve(tor_endpoint, hostname):
642
    """
643
    This is easier to use via :meth:`txtorcon.Tor.dns_resolve`
644

645
    :param tor_endpoint: the Tor SOCKS endpoint to use.
646

647
    :param hostname: the hostname to look up.
648
    """
649
    if six.PY2 and isinstance(hostname, str):
12×
650
        hostname = unicode(hostname)  # noqa
6×
651
    elif six.PY3 and isinstance(hostname, bytes):
12×
UNCOV
652
        hostname = hostname.decode('ascii')
6×
653
    factory = _TorSocksFactory(
12×
654
        hostname, 0, 'RESOLVE', None,
655
    )
656
    proto = yield tor_endpoint.connect(factory)
12×
657
    result = yield proto.when_done()
12×
658
    returnValue(result)
12×
659

660

661
@inlineCallbacks
12×
662
def resolve_ptr(tor_endpoint, ip):
663
    """
664
    This is easier to use via :meth:`txtorcon.Tor.dns_resolve_ptr`
665

666
    :param tor_endpoint: the Tor SOCKS endpoint to use.
667

668
    :param ip: the IP address to look up.
669
    """
670
    if six.PY2 and isinstance(ip, str):
12×
671
        ip = unicode(ip)  # noqa
6×
672
    elif six.PY3 and isinstance(ip, bytes):
12×
UNCOV
673
        ip = ip.decode('ascii')
6×
674
    factory = _TorSocksFactory(
12×
675
        ip, 0, 'RESOLVE_PTR', None,
676
    )
677
    proto = yield tor_endpoint.connect(factory)
12×
678
    result = yield proto.when_done()
12×
679
    returnValue(result)
12×
680

681

682
@implementer(IStreamClientEndpoint)
12×
683
class TorSocksEndpoint(object):
12×
684
    """
685
    Represents an endpoint which will talk to a Tor SOCKS port.
686

687
    These should usually not be instantiated directly, instead use
688
    :meth:`txtorcon.TorConfig.socks_endpoint`.
689
    """
690
    # XXX host, port args should be (host, port) tuple, or
691
    # IAddress-implementer?
692
    def __init__(self, socks_endpoint, host, port, tls=False):
12×
693
        self._proxy_ep = socks_endpoint  # can be Deferred
12×
694
        assert self._proxy_ep is not None
12×
695
        if six.PY2 and isinstance(host, str):
12×
696
            host = unicode(host)  # noqa
6×
697
        if six.PY3 and isinstance(host, bytes):
12×
UNCOV
698
            host = host.decode('ascii')
6×
699
        self._host = host
12×
700
        self._port = port
12×
701
        self._tls = tls
12×
702
        self._socks_factory = None
12×
703
        self._when_address = util.SingleObserver()
12×
704

705
    def _get_address(self):
12×
706
        """
707
        Returns a Deferred that fires with the source IAddress of the
708
        underlying SOCKS connection (i.e. usually a
709
        twisted.internet.address.IPv4Address)
710

711
        circuit.py uses this; better suggestions welcome!
712
        """
713
        return self._when_address.when_fired()
12×
714

715
    @inlineCallbacks
12×
716
    def connect(self, factory):
717
        # further wrap the protocol if we're doing TLS.
718
        # "pray i do not wrap the protocol further".
719
        if self._tls:
12×
720
            # XXX requires Twisted 14+
721
            from twisted.internet.ssl import optionsForClientTLS
12×
722
            if self._tls is True:
12×
723
                context = optionsForClientTLS(self._host)
12×
724
            else:
725
                context = self._tls
12×
726
            tls_factory = tls.TLSMemoryBIOFactory(context, True, factory)
12×
727
            socks_factory = _TorSocksFactory(
12×
728
                self._host, self._port, 'CONNECT', tls_factory,
729
            )
730
        else:
731
            socks_factory = _TorSocksFactory(
12×
732
                self._host, self._port, 'CONNECT', factory,
733
            )
734

735
        self._socks_factory = socks_factory
12×
736
        # forward our address (when we get it) to any listeners
737
        self._socks_factory._get_address().addBoth(self._when_address.fire)
12×
738
        # XXX isn't this just maybeDeferred()
739
        if isinstance(self._proxy_ep, Deferred):
12×
740
            proxy_ep = yield self._proxy_ep
12×
741
            if not IStreamClientEndpoint.providedBy(proxy_ep):
12×
742
                raise ValueError(
12×
743
                    "The Deferred provided as 'socks_endpoint' must "
744
                    "resolve to an IStreamClientEndpoint provider (got "
745
                    "{})".format(type(proxy_ep).__name__)
746
                )
747
        else:
748
            proxy_ep = self._proxy_ep
12×
749

750
        # socks_proto = yield proxy_ep.connect(socks_factory)
751
        proto = yield proxy_ep.connect(socks_factory)
12×
752
        wrapped_proto = yield proto.when_done()
12×
753
        if self._tls:
12×
754
            returnValue(wrapped_proto.wrappedProtocol)
12×
755
        else:
756
            returnValue(wrapped_proto)
12×
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
BLOG · TWITTER · Legal & Privacy · Supported CI Services · What's a CI service? · Automated Testing

© 2022 Coveralls, Inc