|
1 | | -import sys |
2 | | -import itertools |
3 | | - |
4 | | -from twisted.internet import defer, reactor |
| 1 | +import txtorcon |
| 2 | +from twisted.internet import defer, reactor, endpoints |
5 | 3 | from txtorcon.interface import CircuitListenerMixin, IStreamAttacher, StreamListenerMixin |
6 | | -from txtorcon import TorState, launch_tor |
7 | | -from txtorcon.util import available_tcp_port |
8 | 4 | from zope.interface import implementer |
9 | 5 |
|
10 | | - |
11 | | -FETCH_ALL_DESCRIPTOR_OPTIONS = { |
12 | | - 'UseMicroDescriptors': 0, |
13 | | - 'FetchUselessDescriptors': 1, |
14 | | - 'FetchDirInfoEarly': 1, |
15 | | - 'FetchDirInfoExtraEarly': 1, |
16 | | -} |
| 6 | +from bwscanner.logger import log |
17 | 7 |
|
18 | 8 |
|
19 | 9 | @implementer(IStreamAttacher) |
@@ -118,47 +108,80 @@ def stream_closed(self, *args, **kw): |
118 | 108 | self.circ.close(ifUnused=True) |
119 | 109 |
|
120 | 110 |
|
121 | | -def start_tor(config): |
| 111 | +def options_need_new_consensus(tor_config, new_options): |
122 | 112 | """ |
123 | | - Launches tor with random TCP ports chosen for SocksPort and ControlPort, |
124 | | - and other options specified by a txtorcon.torconfig.TorConfig instance. |
125 | | -
|
126 | | - Returns a deferred that calls back with a txtorcon.torstate.TorState |
127 | | - instance. |
| 113 | + Check if we need to wait for a new consensus after updating |
| 114 | + the Tor config with the new options. |
128 | 115 | """ |
129 | | - def get_random_tor_ports(): |
130 | | - d2 = available_tcp_port(reactor) |
131 | | - d2.addCallback(lambda port: config.__setattr__('SocksPort', port)) |
132 | | - d2.addCallback(lambda _: available_tcp_port(reactor)) |
133 | | - d2.addCallback(lambda port: config.__setattr__('ControlPort', port)) |
134 | | - return d2 |
| 116 | + if "UseMicroDescriptors" in new_options: |
| 117 | + if tor_config.UseMicroDescriptors != new_options["UseMicroDescriptors"]: |
| 118 | + log.debug("Changing UseMicroDescriptors from {current} to {new}.", |
| 119 | + current=tor_config.UseMicroDescriptors, |
| 120 | + new=new_options["UseMicroDescriptors"]) |
| 121 | + return True |
| 122 | + return False |
| 123 | + |
135 | 124 |
|
136 | | - def launch_and_get_state(ignore): |
137 | | - d2 = launch_tor(config, reactor, stdout=sys.stdout) |
138 | | - d2.addCallback(lambda tpp: TorState(tpp.tor_protocol).post_bootstrap) |
139 | | - return d2 |
140 | | - return get_random_tor_ports().addCallback(launch_and_get_state) |
| 125 | +def wait_for_newconsensus(tor_state): |
| 126 | + got_consensus = defer.Deferred() |
141 | 127 |
|
| 128 | + def got_newconsensus(event): |
| 129 | + log.debug("Got NEWCONSENSUS event: {event}", event=event) |
| 130 | + got_consensus.callback(event) |
| 131 | + tor_state.protocol.remove_event_listener('NEWCONSENSUS', got_newconsensus) |
142 | 132 |
|
143 | | -def update_tor_config(tor, config): |
| 133 | + tor_state.protocol.add_event_listener('NEWCONSENSUS', got_newconsensus) |
| 134 | + return got_consensus |
| 135 | + |
| 136 | + |
| 137 | +@defer.inlineCallbacks |
| 138 | +def connect_to_tor(launch_tor, circuit_build_timeout, control_port=None, |
| 139 | + tor_overrides=None): |
144 | 140 | """ |
145 | | - Update the Tor config from a dict of config key: value pairs. |
| 141 | + Launch or connect to a Tor instance |
| 142 | +
|
| 143 | + Configure Tor with the passed options and return a Deferred |
146 | 144 | """ |
147 | | - config_pairs = [(key, value) for key, value in config.items()] |
148 | | - d = tor.protocol.set_conf(*itertools.chain.from_iterable(config_pairs)) |
149 | | - return d.addCallback(lambda result: tor) |
150 | | - |
151 | | - |
152 | | -def setconf_singleport_exit(tor): |
153 | | - port = available_tcp_port(reactor) |
154 | | - |
155 | | - def add_single_port_exit(port): |
156 | | - tor.protocol.set_conf('PublishServerDescriptor', '0', |
157 | | - 'PortForwarding', '1', |
158 | | - 'AssumeReachable', '1', |
159 | | - 'ClientRejectInternalAddresses', '0', |
160 | | - 'OrPort', 'auto', |
161 | | - 'ExitPolicyRejectPrivate', '0', |
162 | | - 'ExitPolicy', 'accept 127.0.0.1:{}, reject *:*'.format(port)) |
163 | | - return port.addCallback(add_single_port_exit).addCallback( |
164 | | - lambda ign: tor.routers[tor.protocol.get_info("fingerprint")]) |
| 145 | + # Options for spawned or running Tor to load the correct descriptors. |
| 146 | + tor_options = { |
| 147 | + 'LearnCircuitBuildTimeout': 0, # Disable adaptive circuit timeouts. |
| 148 | + 'CircuitBuildTimeout': circuit_build_timeout, |
| 149 | + 'UseEntryGuards': 0, # Disable UseEntryGuards to avoid PathBias warnings. |
| 150 | + 'UseMicroDescriptors': 0, |
| 151 | + 'FetchUselessDescriptors': 1, |
| 152 | + 'FetchDirInfoEarly': 1, |
| 153 | + 'FetchDirInfoExtraEarly': 1, |
| 154 | + } |
| 155 | + |
| 156 | + if tor_overrides: |
| 157 | + tor_options.update(tor_overrides) |
| 158 | + |
| 159 | + if launch_tor: |
| 160 | + log.info("Spawning a new Tor instance.") |
| 161 | + # TODO: Pass in data_dir directory so consensus can be cached |
| 162 | + tor = yield txtorcon.launch(reactor) |
| 163 | + else: |
| 164 | + log.info("Trying to connect to a running Tor instance.") |
| 165 | + if control_port: |
| 166 | + endpoint = endpoints.TCP4ClientEndpoint(reactor, "localhost", control_port) |
| 167 | + else: |
| 168 | + endpoint = None |
| 169 | + tor = yield txtorcon.connect(reactor, endpoint) |
| 170 | + |
| 171 | + # Get Tor state first to avoid a race conditions where CONF_CHANGED |
| 172 | + # messages are received while Txtorcon is reading the consensus. |
| 173 | + tor_state = yield tor.create_state() |
| 174 | + |
| 175 | + # Get current TorConfig object |
| 176 | + tor_config = yield tor.get_config() |
| 177 | + wait_for_consensus = options_need_new_consensus(tor_config, tor_options) |
| 178 | + |
| 179 | + # Update Tor config options from dictionary |
| 180 | + for key, value in tor_options.items(): |
| 181 | + setattr(tor_config, key, value) |
| 182 | + yield tor_config.save() # Send updated options to Tor |
| 183 | + |
| 184 | + if wait_for_consensus: |
| 185 | + yield wait_for_newconsensus(tor_state) |
| 186 | + |
| 187 | + defer.returnValue(tor_state) |
0 commit comments