~ubuntu-branches/ubuntu/trusty/txtorcon/trusty-proposed

« back to all changes in this revision

Viewing changes to examples/foo.py~

  • Committer: Package Import Robot
  • Author(s): Jérémy Bobbio
  • Date: 2014-01-21 15:10:52 UTC
  • mfrom: (1.1.3)
  • Revision ID: package-import@ubuntu.com-20140121151052-r1dw06if9912ihbp
Tags: 0.9.1-1
* New upstream release.
* Update debian/watch and verify upstream signature
* Improve debian/README.source

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
 
3
 
##
4
 
## This uses a custom txtorcon.IStreamAttacher to force streams to use
5
 
## circuits that exit in the same country (as supplied by GeoIP) and
6
 
## builds such a circuit if one isn't available yet.
7
 
##
8
 
## Note that you can do something very similar to this with Tor's
9
 
## config file as well by setting something like:
10
 
##
11
 
## ExitNodes {us},{ca}
12
 
##
13
 
## ...in your torrc. The above just exits from those countries, not
14
 
## the one in which the Web server is located, however. So, this is a
15
 
## little redundant, but gives you the idea of how to do these sorts
16
 
## of things.
17
 
##
18
 
## Another thing to note is that the DNS lookup is a stream before the
19
 
## name is looked up, so the DNS lookup may occur from whatever stream
20
 
## Tor chose for that (we return None, which causes the attacher to
21
 
## tell Tor to attach that stream itself). This presents a problem for
22
 
## sites which optimize the server they deliver based on DNS -- if you
23
 
## lookup from X you'll get a server near/in X, which for our next
24
 
## step will make "the site" appear to be there.
25
 
##
26
 
## The only "solution" for this would be to do the lookup locally, but
27
 
## that defeats the purpose of Tor.
28
 
##
29
 
 
30
 
import random
31
 
 
32
 
from twisted.python import log
33
 
from twisted.internet import reactor, defer
34
 
from zope.interface import implements
35
 
 
36
 
import txtorcon
37
 
 
38
 
 
39
 
class MyStreamListener(txtorcon.StreamListenerMixin):
40
 
 
41
 
    def stream_new(self, stream):
42
 
        print "new stream:", stream.id, stream.target_host
43
 
 
44
 
    def stream_succeeded(self, stream):
45
 
        print "successful stream:", stream.id, stream.target_host
46
 
 
47
 
    def stream_attach(self, stream, circuit):
48
 
        print "stream", stream.id, " attached to circuit", circuit.id,
49
 
        print "with path:", '->'.join(map(lambda x: x.location.countrycode, circuit.path))
50
 
 
51
 
 
52
 
class MyAttacher(txtorcon.CircuitListenerMixin):
53
 
    implements(txtorcon.IStreamAttacher)
54
 
 
55
 
    def __init__(self, state):
56
 
        ## pointer to our TorState object
57
 
        self.state = state
58
 
        ## circuits for which we are awaiting completion so we can
59
 
        ## finish our attachment to them.
60
 
        self.waiting_circuits = []
61
 
 
62
 
    def waiting_on(self, circuit):
63
 
        for (circid, d, stream_cc) in self.waiting_circuits:
64
 
            if circuit.id == circid:
65
 
                return True
66
 
        return False
67
 
 
68
 
    def circuit_extend(self, circuit, router):
69
 
        "ICircuitListener"
70
 
        if circuit.purpose != 'GENERAL':
71
 
            return
72
 
        # only output for circuits we're waiting on
73
 
        if self.waiting_on(circuit):
74
 
            print "  circuit %d (%s). Path now %s" % (circuit.id, router.id_hex,
75
 
                                                      '->'.join(map(lambda x: x.location.countrycode,
76
 
                                                                    circuit.path)))
77
 
 
78
 
    def circuit_built(self, circuit):
79
 
        "ICircuitListener"
80
 
        if circuit.purpose != 'GENERAL':
81
 
            return
82
 
 
83
 
        print "circuit built", circuit.id, '->'.join(map(lambda r:
84
 
                                                         r.location.countrycode,
85
 
                                                         circuit.path))
86
 
        for (circid, d, stream_cc) in self.waiting_circuits:
87
 
            if circid == circuit.id:
88
 
                self.waiting_circuits.remove((circid, d, stream_cc))
89
 
                d.callback(circuit)
90
 
 
91
 
    def circuit_failed(self, circuit, kw):
92
 
        if self.waiting_on(circuit):
93
 
            print "A circuit we requested", circuit.id, "has failed. Reason:", kw['REASON']
94
 
 
95
 
            circid, d, stream_cc = None, None, None
96
 
            for x in self.waiting_circuits:
97
 
                if x[0] == circuit.id:
98
 
                    circid, d, stream_cc = x
99
 
            if d is None:
100
 
                raise Exception("Expected to find circuit.")
101
 
 
102
 
            self.waiting_circuits.remove((circid, d, stream_cc))
103
 
            print "Trying a new circuit build for", circid
104
 
            self.request_circuit_build(stream_cc, d)
105
 
 
106
 
    def attach_stream(self, stream, circuits):
107
 
        """
108
 
        IStreamAttacher API
109
 
        """
110
 
        if not stream.target_host in self.state.addrmap.addr:
111
 
            print "No AddrMap entry for", stream.target_host,
112
 
            print "so I don't know where it exits; get Tor to attach stream."
113
 
            return None
114
 
 
115
 
        ip = str(self.state.addrmap.addr[stream.target_host].ip)
116
 
        stream_cc = txtorcon.util.NetLocation(ip).countrycode
117
 
        print "Stream to", ip, "exiting in", stream_cc
118
 
 
119
 
        if stream_cc is None:
120
 
            ## returning None tells TorState to ask Tor to select a
121
 
            ## circuit instead
122
 
            print "   unknown country, Tor will assign stream"
123
 
            return None
124
 
 
125
 
        for circ in circuits.values():
126
 
            if circ.state != 'BUILT' or circ.purpose != 'GENERAL':
127
 
                continue
128
 
 
129
 
            circuit_cc = circ.path[-1].location.countrycode
130
 
            if circuit_cc is None:
131
 
                print "warning: don't know where circuit", circ.id, "exits"
132
 
 
133
 
            if circuit_cc == stream_cc:
134
 
                print "  found suitable circuit:", circ
135
 
                return circ
136
 
 
137
 
        ## if we get here, we haven't found a circuit that exits in
138
 
        ## the country GeoIP claims our target server is in, so we
139
 
        ## need to build one.
140
 
        print "Didn't find a circuit, building one"
141
 
 
142
 
        ## we need to return a Deferred which will callback with our
143
 
        ## circuit, however built_circuit only callbacks with the
144
 
        ## message from Tor saying it heard about our request. So when
145
 
        ## that happens, we push our real Deferred into the
146
 
        ## waiting_circuits list which will get pop'd at some point
147
 
        ## when the circuit_built() listener callback happens.
148
 
 
149
 
        d = defer.Deferred()
150
 
        self.request_circuit_build(stream_cc, d)
151
 
        return d
152
 
 
153
 
    def request_circuit_build(self, stream_cc, deferred_to_callback):
154
 
        ## for exits, we can select from any router that's in the
155
 
        ## correct country.
156
 
        last = filter(lambda x: x.location.countrycode == stream_cc,
157
 
                      self.state.routers.values())
158
 
 
159
 
        ## start with an entry guard, put anything in the middle and
160
 
        ## put one of our exits at the end.
161
 
        path = [random.choice(self.state.entry_guards.values()),
162
 
                random.choice(self.state.routers.values()),
163
 
                random.choice(last)]
164
 
 
165
 
        print "  requesting a circuit:", '->'.join(map(lambda r:
166
 
                                                       r.location.countrycode,
167
 
                                                       path))
168
 
 
169
 
        class AppendWaiting:
170
 
            def __init__(self, attacher, d, stream_cc):
171
 
                self.attacher = attacher
172
 
                self.d = d
173
 
                self.stream_cc = stream_cc
174
 
 
175
 
            def __call__(self, circ):
176
 
                """
177
 
                return from build_circuit is a Circuit. However, we
178
 
                want to wait until it is built before we can issue an
179
 
                attach on it and callback to the Deferred we issue
180
 
                here.
181
 
                """
182
 
                print "  my circuit is in progress", circ.id
183
 
                self.attacher.waiting_circuits.append((circ.id, self.d,
184
 
                                                       self.stream_cc))
185
 
 
186
 
        return self.state.build_circuit(path).addCallback(AppendWaiting(self, deferred_to_callback, stream_cc)).addErrback(log.err)
187
 
 
188
 
 
189
 
def do_setup(state):
190
 
    print "Connected to a Tor version", state.protocol.version
191
 
 
192
 
    attacher = MyAttacher(state)
193
 
    state.set_attacher(attacher, reactor)
194
 
    state.add_circuit_listener(attacher)
195
 
 
196
 
    state.add_stream_listener(MyStreamListener())
197
 
 
198
 
    print "Existing state when we connected:"
199
 
    print "Streams:"
200
 
    for s in state.streams.values():
201
 
        print ' ', s
202
 
 
203
 
    print
204
 
    print "General-purpose circuits:"
205
 
    for c in filter(lambda x: x.purpose == 'GENERAL', state.circuits.values()):
206
 
        print ' ', c.id, '->'.join(map(lambda x: x.location.countrycode,
207
 
                                       c.path))
208
 
 
209
 
 
210
 
def setup_failed(arg):
211
 
    print "SETUP FAILED", arg
212
 
    reactor.stop()
213
 
 
214
 
d = txtorcon.build_local_tor_connection(reactor)
215
 
d.addCallback(do_setup).addErrback(setup_failed)
216
 
reactor.run()