574 lines
23 KiB
Python
574 lines
23 KiB
Python
|
#!/usr/bin/env python3
|
||
|
|
||
|
import random # For simulation, not cryptography!
|
||
|
import math
|
||
|
import sys
|
||
|
import time
|
||
|
import logging
|
||
|
|
||
|
import network
|
||
|
import dirauth
|
||
|
import relay
|
||
|
|
||
|
import cell as simcell
|
||
|
import msg as simmsg
|
||
|
|
||
|
import nacl.hash
|
||
|
|
||
|
class VanillaCreatedExtendedHandler:
|
||
|
"""A handler for VanillaCreatedCircuitCell and
|
||
|
VanillaExtendedCircuitCell cells."""
|
||
|
|
||
|
def __init__(self, channelmgr, ntor, expecteddesc):
|
||
|
self.channelmgr = channelmgr
|
||
|
self.ntor = ntor
|
||
|
self.expecteddesc = expecteddesc
|
||
|
self.onionkey = expecteddesc.descdict['onionkey']
|
||
|
self.idkey = expecteddesc.descdict['idkey']
|
||
|
|
||
|
def received_cell(self, circhandler, cell):
|
||
|
secret = self.ntor.verify(cell.ntor_reply, self.onionkey, self.idkey)
|
||
|
enckey = nacl.hash.sha256(secret + b'upstream')
|
||
|
deckey = nacl.hash.sha256(secret + b'downstream')
|
||
|
circhandler.add_crypt_layer(enckey, deckey)
|
||
|
if len(circhandler.circuit_descs) == 0:
|
||
|
# This was a VanillaCreatedCircuitCell
|
||
|
circhandler.replace_celltype_handler(
|
||
|
simcell.VanillaCreatedCircuitCell, None)
|
||
|
else:
|
||
|
# This was a VanillaExtendedCircuitCell
|
||
|
circhandler.replace_celltype_handler(
|
||
|
simcell.VanillaExtendedCircuitCell, None)
|
||
|
circhandler.circuit_descs.append(self.expecteddesc)
|
||
|
|
||
|
# Are we done building the circuit?
|
||
|
if len(circhandler.circuit_descs) == 3:
|
||
|
# Yes!
|
||
|
return
|
||
|
|
||
|
nexthop = None
|
||
|
while nexthop is None:
|
||
|
nexthop = self.channelmgr.relaypicker.pick_weighted_relay()
|
||
|
if nexthop.descdict['addr'] in \
|
||
|
[ desc.descdict['addr'] \
|
||
|
for desc in circhandler.circuit_descs ]:
|
||
|
nexthop = None
|
||
|
|
||
|
# Construct the VanillaExtendCircuitCell
|
||
|
ntor = relay.NTor(self.channelmgr.perfstats)
|
||
|
ntor_request = ntor.request()
|
||
|
circextendmsg = simcell.VanillaExtendCircuitCell(
|
||
|
nexthop.descdict['addr'], ntor_request)
|
||
|
|
||
|
# Set up the reply handler
|
||
|
circhandler.replace_celltype_handler(
|
||
|
simcell.VanillaExtendedCircuitCell,
|
||
|
VanillaCreatedExtendedHandler(self.channelmgr, ntor, nexthop))
|
||
|
|
||
|
# Send the cell
|
||
|
circhandler.send_cell(circextendmsg)
|
||
|
|
||
|
class TelescopingCreatedHandler:
|
||
|
"""A handler for TelescopingCreatedCircuitCell cells; this will only always
|
||
|
communicate with the client's guard."""
|
||
|
|
||
|
def __init__(self, channelmgr, ntor):
|
||
|
self.channelmgr = channelmgr
|
||
|
self.ntor = ntor
|
||
|
if type(self.channelmgr.guard) is dirauth.RelayDescriptor:
|
||
|
guardd = self.channelmgr.guard.descdict
|
||
|
else:
|
||
|
guardd = self.channelmgr.guard.snipdict
|
||
|
self.onionkey = guardd["onionkey"]
|
||
|
self.idkey = guardd["idkey"]
|
||
|
|
||
|
def received_cell(self, circhandler, cell):
|
||
|
logging.debug("Received cell in TelescopingCreatedHandler")
|
||
|
|
||
|
secret = self.ntor.verify(cell.ntor_reply, self.onionkey, self.idkey)
|
||
|
enckey = nacl.hash.sha256(secret + b'upstream')
|
||
|
deckey = nacl.hash.sha256(secret + b'downstream')
|
||
|
circhandler.add_crypt_layer(enckey, deckey)
|
||
|
|
||
|
circhandler.replace_celltype_handler(simcell.TelescopingCreatedCircuitCell, None)
|
||
|
|
||
|
circhandler.circuit_descs.append(self.channelmgr.guard)
|
||
|
|
||
|
nexthopidx = None
|
||
|
while nexthopidx is None:
|
||
|
nexthopidx = self.channelmgr.relaypicker.pick_weighted_relay_index()
|
||
|
#print("WARNING: Unimplemented! Need to check if this idx is in the list of circhandlers idxs")
|
||
|
# TODO verify we don't need to do the above
|
||
|
|
||
|
# Construct the TelescopingExtendCircuitCell
|
||
|
ntor = relay.NTor(self.channelmgr.perfstats)
|
||
|
ntor_request = ntor.request()
|
||
|
circextendmsg = simcell.TelescopingExtendCircuitCell(
|
||
|
nexthopidx, ntor_request)
|
||
|
|
||
|
# Set up the reply handler
|
||
|
circhandler.replace_celltype_handler(
|
||
|
simcell.TelescopingExtendedCircuitCell,
|
||
|
TelescopingExtendedHandler(self.channelmgr, ntor))
|
||
|
|
||
|
# Send the cell
|
||
|
circhandler.send_cell(circextendmsg)
|
||
|
|
||
|
|
||
|
class TelescopingExtendedHandler:
|
||
|
"""A handler for TelescopingExtendedCircuitCell cells."""
|
||
|
|
||
|
def __init__(self, channelmgr, ntor):
|
||
|
self.channelmgr = channelmgr
|
||
|
self.ntor = ntor
|
||
|
|
||
|
def received_cell(self, circhandler, cell):
|
||
|
logging.debug("Received cell in TelescopingExtendedHandler")
|
||
|
|
||
|
# Validate the SNIP
|
||
|
dirauth.SNIP.verify(cell.snip, self.channelmgr.consensus,
|
||
|
network.thenetwork.dirauthkeys()[0],
|
||
|
self.channelmgr.perfstats)
|
||
|
|
||
|
onionkey = cell.snip.snipdict['onionkey']
|
||
|
idkey = cell.snip.snipdict['idkey']
|
||
|
|
||
|
secret = self.ntor.verify(cell.ntor_reply, onionkey, idkey)
|
||
|
enckey = nacl.hash.sha256(secret + b'upstream')
|
||
|
deckey = nacl.hash.sha256(secret + b'downstream')
|
||
|
circhandler.add_crypt_layer(enckey, deckey)
|
||
|
|
||
|
circhandler.replace_celltype_handler(
|
||
|
simcell.TelescopingExtendedCircuitCell, None)
|
||
|
circhandler.circuit_descs.append(cell.snip)
|
||
|
|
||
|
# Are we done building the circuit?
|
||
|
#logging.warning("we may need another circhandler structure for snips")
|
||
|
if len(circhandler.circuit_descs) == 3:
|
||
|
# Yes!
|
||
|
return
|
||
|
|
||
|
nexthopidx = self.channelmgr.relaypicker.pick_weighted_relay_index()
|
||
|
|
||
|
# Construct the VanillaExtendCircuitCell
|
||
|
ntor = relay.NTor(self.channelmgr.perfstats)
|
||
|
ntor_request = ntor.request()
|
||
|
circextendmsg = simcell.TelescopingExtendCircuitCell(
|
||
|
nexthopidx, ntor_request)
|
||
|
|
||
|
# Set up the reply handler
|
||
|
circhandler.replace_celltype_handler(
|
||
|
simcell.TelescopingExtendedCircuitCell,
|
||
|
TelescopingExtendedHandler(self.channelmgr, ntor))
|
||
|
|
||
|
# Send the cell
|
||
|
circhandler.send_cell(circextendmsg)
|
||
|
|
||
|
class SinglePassCreatedHandler:
|
||
|
"""A handler for SinglePassCreatedCircuitCell cells."""
|
||
|
|
||
|
def __init__(self, channelmgr, ntor, client_key):
|
||
|
self.channelmgr = channelmgr
|
||
|
self.ntor = ntor
|
||
|
self.client_key = client_key
|
||
|
|
||
|
def received_cell(self, circhandler, cell):
|
||
|
# We should only get one simcell.SinglePassCreatedCircuitCell per
|
||
|
# circuit
|
||
|
circhandler.replace_celltype_handler(simcell.SinglePassCreatedCircuitCell, None)
|
||
|
|
||
|
# The circuit always starts with the guard
|
||
|
circhandler.circuit_descs.append(self.channelmgr.guard)
|
||
|
|
||
|
# Process each layer of the message
|
||
|
blinding_keys = []
|
||
|
while cell is not None:
|
||
|
lasthop = circhandler.circuit_descs[-1]
|
||
|
if type(lasthop) is dirauth.RelayDescriptor:
|
||
|
lasthopd = lasthop.descdict
|
||
|
else:
|
||
|
lasthopd = lasthop.snipdict
|
||
|
onionkey = lasthopd["onionkey"]
|
||
|
idkey = lasthopd["idkey"]
|
||
|
pathselkey = lasthopd["pathselkey"]
|
||
|
if cell.enc is None:
|
||
|
secret = self.ntor.verify(cell.ntor_reply, onionkey, idkey)
|
||
|
enckey = nacl.hash.sha256(secret + b'upstream')
|
||
|
deckey = nacl.hash.sha256(secret + b'downstream')
|
||
|
circhandler.add_crypt_layer(enckey, deckey)
|
||
|
cell = None
|
||
|
else:
|
||
|
secret = self.ntor.verify(cell.ntor_reply, onionkey,
|
||
|
idkey, b'circuit')
|
||
|
enckey = nacl.hash.sha256(secret + b'upstream')
|
||
|
deckey = nacl.hash.sha256(secret + b'downstream')
|
||
|
createdkey = nacl.hash.sha256(secret + b'created')
|
||
|
circhandler.add_crypt_layer(enckey, deckey)
|
||
|
(snip, vrfout, nextlayer) = cell.enc.decrypt(createdkey)
|
||
|
# Check the signature on the SNIP
|
||
|
dirauth.SNIP.verify(snip, self.channelmgr.consensus,
|
||
|
network.thenetwork.dirauthkeys()[0],
|
||
|
self.channelmgr.perfstats)
|
||
|
# Compute the index, check the VRF, ensure the SNIP is
|
||
|
# the correct one
|
||
|
pathsel_rand, next_blindkey = relay.Sphinx.client(
|
||
|
self.client_key, blinding_keys,
|
||
|
onionkey, b'pathsel',
|
||
|
nextlayer is None, self.channelmgr.perfstats)
|
||
|
if nextlayer is not None:
|
||
|
blinding_keys.append(next_blindkey)
|
||
|
|
||
|
try:
|
||
|
index = int.from_bytes(relay.VRF.check_output(pathselkey,
|
||
|
pathsel_rand, vrfout,
|
||
|
self.channelmgr.perfstats)[:4],
|
||
|
'big', signed=False)
|
||
|
except ValueError as e:
|
||
|
circhandler.close()
|
||
|
raise ValueError(str(e.args) + str(lasthopd))
|
||
|
|
||
|
indexrange = snip.snipdict["range"]
|
||
|
if index < indexrange[0] or index >= indexrange[1]:
|
||
|
logging.error("Incorrect SNIP received")
|
||
|
|
||
|
circhandler.circuit_descs.append(snip)
|
||
|
cell = nextlayer
|
||
|
|
||
|
|
||
|
class ClientChannelManager(relay.ChannelManager):
|
||
|
"""The subclass of ChannelManager for clients."""
|
||
|
|
||
|
def __init__(self, myaddr, dirauthaddrs, perfstats):
|
||
|
super().__init__(myaddr, dirauthaddrs, perfstats)
|
||
|
self.guardaddr = None
|
||
|
self.guard = None
|
||
|
self.circuit_build_begin = 0
|
||
|
|
||
|
def get_consensus_from_fallbackrelay(self):
|
||
|
"""Download a fresh consensus from a random fallbackrelay."""
|
||
|
fb = network.thenetwork.getfallbackrelay()
|
||
|
logging.debug("Chose fallback %s", fb)
|
||
|
if network.thenetwork.womode == network.WOMode.VANILLA:
|
||
|
if self.consensus is not None and \
|
||
|
len(self.consensus.consdict['relays']) > 0:
|
||
|
self.send_msg(simmsg.RelayGetConsensusDiffMsg(), fb.netaddr)
|
||
|
else:
|
||
|
self.send_msg(simmsg.RelayGetConsensusMsg(), fb.netaddr)
|
||
|
else:
|
||
|
self.send_msg(simmsg.RelayGetConsensusMsg(), fb.netaddr)
|
||
|
|
||
|
def ensure_guard_vanilla(self):
|
||
|
"""Ensure that we have a channel to a guard (Vanilla Onion
|
||
|
Routing version)."""
|
||
|
while True:
|
||
|
if self.guardaddr is None:
|
||
|
# Pick a guard from the consensus
|
||
|
self.guard = self.relaypicker.pick_weighted_relay()
|
||
|
self.guardaddr = self.guard.descdict['addr']
|
||
|
|
||
|
self.test_guard_connection()
|
||
|
|
||
|
if self.guardaddr is not None:
|
||
|
break
|
||
|
|
||
|
logging.debug('chose guard=%s', self.guardaddr)
|
||
|
|
||
|
def test_guard_connection(self):
|
||
|
# Connect to the guard
|
||
|
try:
|
||
|
self.get_channel_to(self.guardaddr)
|
||
|
except network.NetNoServer:
|
||
|
# Our guard is gone
|
||
|
self.guardaddr = None
|
||
|
self.guard = None
|
||
|
|
||
|
def ensure_guard_walking_onions(self):
|
||
|
"""Ensure we have a channel to a guard (Walking Onions version).
|
||
|
|
||
|
For the first implementation, we assume an out-of-band mechanism
|
||
|
that just simply hands us a guard; we don't count the number of
|
||
|
operations or bandwidth as this operation in practice occurs
|
||
|
infrequently."""
|
||
|
|
||
|
while True:
|
||
|
if self.guardaddr is None:
|
||
|
#randomly sample a guard
|
||
|
#logging.warning("Unimplemented! guard should be selected from any relays.")
|
||
|
self.guard = self.relaypicker.pick_weighted_relay()
|
||
|
# here, we have a SNIP instead of a relay descriptor
|
||
|
self.guardaddr = self.guard.snipdict['addr']
|
||
|
|
||
|
self.test_guard_connection()
|
||
|
|
||
|
if self.guardaddr is not None:
|
||
|
break
|
||
|
|
||
|
# Ensure we have the current descriptor for the guard
|
||
|
# Note that self.guard may be a RelayDescriptor or a SNIP,
|
||
|
# depending on how we got it
|
||
|
if type(self.guard) is dirauth.RelayDescriptor:
|
||
|
guardepoch = self.guard.descdict["epoch"]
|
||
|
else:
|
||
|
guardepoch = self.guard.snipdict["epoch"]
|
||
|
if guardepoch != network.thenetwork.getepoch():
|
||
|
guardchannel = self.get_channel_to(self.guardaddr)
|
||
|
guardchannel.send_msg(simmsg.RelayGetDescMsg())
|
||
|
|
||
|
logging.debug('chose guard=%s', self.guardaddr)
|
||
|
|
||
|
def ensure_guard(self):
|
||
|
"""Ensure that we have a channel to a guard."""
|
||
|
if network.thenetwork.womode == network.WOMode.VANILLA:
|
||
|
self.ensure_guard_vanilla()
|
||
|
return
|
||
|
# At this point, we are either in Telescoping or Single-Pass mode
|
||
|
|
||
|
self.ensure_guard_walking_onions()
|
||
|
|
||
|
def new_circuit_vanilla(self):
|
||
|
"""Create a new circuit from this client. (Vanilla Onion Routing
|
||
|
version)"""
|
||
|
|
||
|
# Get our channel to the guard
|
||
|
guardchannel = self.get_channel_to(self.guardaddr)
|
||
|
|
||
|
# Allocate a new circuit id on it
|
||
|
circid, circhandler = guardchannel.new_circuit()
|
||
|
|
||
|
# Construct the VanillaCreateCircuitMsg
|
||
|
ntor = relay.NTor(self.perfstats)
|
||
|
ntor_request = ntor.request()
|
||
|
circcreatemsg = simmsg.VanillaCreateCircuitMsg(circid, ntor_request)
|
||
|
|
||
|
# Set up the reply handler
|
||
|
circhandler.replace_celltype_handler(
|
||
|
simcell.VanillaCreatedCircuitCell,
|
||
|
VanillaCreatedExtendedHandler(self, ntor, self.guard))
|
||
|
|
||
|
# Send the message
|
||
|
guardchannel.send_msg(circcreatemsg)
|
||
|
|
||
|
if guardchannel.is_circuit_open(circid):
|
||
|
self.perfstats.circuit_building_time = time.perf_counter() - self.circuit_build_begin
|
||
|
logging.debug("Client %s ended circuit building after %s", self.myaddr, str(self.perfstats.circuit_building_time))
|
||
|
|
||
|
return circhandler
|
||
|
|
||
|
def new_circuit_telescoping(self):
|
||
|
"""Create a new circuit from this client (Telescoping Walking Onions
|
||
|
version). If an error occurs and the circuit is deleted from the guard
|
||
|
channel, return None, otherwise, return the circuit handler."""
|
||
|
|
||
|
# Get our channel to the guard
|
||
|
guardchannel = self.get_channel_to(self.guardaddr)
|
||
|
|
||
|
# Allocate a new circuit id on it
|
||
|
circid, circhandler = guardchannel.new_circuit()
|
||
|
|
||
|
# Construct the TelescopingCreateCircuitMsg
|
||
|
ntor = relay.NTor(self.perfstats)
|
||
|
ntor_request = ntor.request()
|
||
|
circcreatemsg = simmsg.TelescopingCreateCircuitMsg(circid, ntor_request)
|
||
|
|
||
|
# Set up the reply handler
|
||
|
circhandler.replace_celltype_handler(
|
||
|
simcell.TelescopingCreatedCircuitCell,
|
||
|
TelescopingCreatedHandler(self, ntor))
|
||
|
|
||
|
# Send the message
|
||
|
guardchannel.send_msg(circcreatemsg)
|
||
|
|
||
|
# Check to make sure the circuit is open before sending it- if there
|
||
|
# was an error when establishing it, the circuit could already be
|
||
|
# closed.
|
||
|
if guardchannel.is_circuit_open(circid):
|
||
|
self.perfstats.circuit_building_time = time.perf_counter() - self.circuit_build_begin
|
||
|
logging.debug("Client %s ended circuit building after %s", self.myaddr, str(self.perfstats.circuit_building_time))
|
||
|
else:
|
||
|
logging.debug("Circuit was already closed, not sending bytes. circid: " + str(circid))
|
||
|
return None
|
||
|
|
||
|
guard = circhandler.circuit_descs[0]
|
||
|
if type(guard) is dirauth.RelayDescriptor:
|
||
|
guardd = guard.descdict
|
||
|
else:
|
||
|
guardd = guard.snipdict
|
||
|
if guardd["addr"] == circhandler.circuit_descs[2].snipdict["addr"]:
|
||
|
logging.debug("circuit in a loop")
|
||
|
circhandler.close()
|
||
|
circhandler = None
|
||
|
|
||
|
return circhandler
|
||
|
|
||
|
def new_circuit_singlepass(self):
|
||
|
"""Create a new circuit from this client (Single-Pass Walking Onions
|
||
|
version). If an error occurs and the circuit is deleted from the guard
|
||
|
channel, return None, otherwise, return the circuit handler."""
|
||
|
|
||
|
# Get our channel to the guard
|
||
|
guardchannel = self.get_channel_to(self.guardaddr)
|
||
|
|
||
|
# Allocate a new circuit id on it
|
||
|
circid, circhandler = guardchannel.new_circuit()
|
||
|
|
||
|
# first, create the path-selection key used for Sphinx
|
||
|
client_pathsel_key = nacl.public.PrivateKey.generate()
|
||
|
self.perfstats.keygens += 1
|
||
|
|
||
|
# Construct the SinglePassCreateCircuitMsg
|
||
|
ntor = relay.NTor(self.perfstats)
|
||
|
ntor_request = ntor.request()
|
||
|
circcreatemsg = simmsg.SinglePassCreateCircuitMsg(circid, ntor_request,
|
||
|
client_pathsel_key.public_key)
|
||
|
|
||
|
# Set up the reply handler
|
||
|
circhandler.replace_celltype_handler(
|
||
|
simcell.SinglePassCreatedCircuitCell,
|
||
|
SinglePassCreatedHandler(self, ntor, client_pathsel_key))
|
||
|
|
||
|
# Send the message
|
||
|
guardchannel.send_msg(circcreatemsg)
|
||
|
|
||
|
# Check to make sure the circuit is open before sending it- if there
|
||
|
# was an error when establishing it, the circuit could already be
|
||
|
# closed.
|
||
|
if guardchannel.is_circuit_open(circid):
|
||
|
self.perfstats.circuit_building_time = time.perf_counter() - self.circuit_build_begin
|
||
|
logging.debug("Client %s ended circuit building after %s", self.myaddr, str(self.perfstats.circuit_building_time))
|
||
|
else:
|
||
|
logging.debug("Circuit was already closed, not sending bytes. circid: " + str(circid))
|
||
|
return None
|
||
|
|
||
|
# In Single-Pass Walking Onions, we need to check whether the
|
||
|
# circuit got into a loop (guard equals exit); each node will
|
||
|
# refuse to extend to itself, so this is the only possible loop
|
||
|
# in a circuit of length 3
|
||
|
guard = circhandler.circuit_descs[0]
|
||
|
if type(guard) is dirauth.RelayDescriptor:
|
||
|
guardd = guard.descdict
|
||
|
else:
|
||
|
guardd = guard.snipdict
|
||
|
if guardd["addr"] == circhandler.circuit_descs[2].snipdict["addr"]:
|
||
|
logging.debug("circuit in a loop")
|
||
|
circhandler.close()
|
||
|
circhandler = None
|
||
|
|
||
|
return circhandler
|
||
|
|
||
|
def new_circuit(self):
|
||
|
"""Create a new circuit from this client."""
|
||
|
circhandler = None
|
||
|
|
||
|
# Start tracking the time for building a circuit
|
||
|
self.circuit_build_begin = time.perf_counter()
|
||
|
logging.debug("%s began circuit building after %s", self.myaddr, str(self.circuit_build_begin))
|
||
|
|
||
|
# If an error occured, circhandler will still be None, so we should
|
||
|
# try again.
|
||
|
while circhandler is None:
|
||
|
if network.thenetwork.womode == network.WOMode.VANILLA:
|
||
|
circhandler = self.new_circuit_vanilla()
|
||
|
elif network.thenetwork.womode == network.WOMode.TELESCOPING:
|
||
|
circhandler = self.new_circuit_telescoping()
|
||
|
elif network.thenetwork.womode == network.WOMode.SINGLEPASS:
|
||
|
circhandler = self.new_circuit_singlepass()
|
||
|
|
||
|
return circhandler
|
||
|
|
||
|
def received_msg(self, msg, peeraddr, channel):
|
||
|
"""Callback when a NetMsg not specific to a circuit is
|
||
|
received."""
|
||
|
logging.debug("Client %s received msg %s from %s" % (self.myaddr, msg, peeraddr))
|
||
|
if isinstance(msg, simmsg.RelayConsensusMsg) or \
|
||
|
isinstance(msg, simmsg.RelayConsensusDiffMsg):
|
||
|
self.relaypicker = dirauth.Consensus.verify(msg.consensus,
|
||
|
network.thenetwork.dirauthkeys(), self.perfstats)
|
||
|
self.consensus = msg.consensus
|
||
|
elif isinstance(msg, simmsg.RelayDescMsg):
|
||
|
dirauth.RelayDescriptor.verify(msg.desc, self.perfstats)
|
||
|
self.guard = msg.desc
|
||
|
else:
|
||
|
return super().received_msg(msg, peeraddr, channel)
|
||
|
|
||
|
def received_cell(self, circid, cell, peeraddr, channel):
|
||
|
"""Callback with a circuit-specific cell is received."""
|
||
|
logging.debug("Client %s received cell on circ %d: %s from %s" % (self.myaddr, circid, cell, peeraddr))
|
||
|
if isinstance(cell, simcell.CloseCell):
|
||
|
logging.debug("Log: Client received close cell; closing circuit")
|
||
|
# TODO close cell
|
||
|
return super().received_cell(circid, cell, peeraddr, channel)
|
||
|
|
||
|
|
||
|
class Client:
|
||
|
"""A class representing a Tor client."""
|
||
|
|
||
|
def __init__(self, dirauthaddrs):
|
||
|
# Get a network address for client-side use only (do not bind it
|
||
|
# to the network)
|
||
|
self.netaddr = network.NetAddr()
|
||
|
self.perfstats = network.PerfStats(network.EntType.CLIENT)
|
||
|
self.perfstats.name = "Client at %s" % self.netaddr
|
||
|
self.perfstats.is_bootstrapping = True
|
||
|
self.channelmgr = ClientChannelManager(self.netaddr, dirauthaddrs,
|
||
|
self.perfstats)
|
||
|
|
||
|
# Register for epoch tick notifications
|
||
|
network.thenetwork.wantepochticks(self, True)
|
||
|
|
||
|
def terminate(self):
|
||
|
"""Quit this client."""
|
||
|
|
||
|
# Stop listening for epoch ticks
|
||
|
network.thenetwork.wantepochticks(self, False)
|
||
|
|
||
|
# Close relay connections
|
||
|
self.channelmgr.terminate()
|
||
|
|
||
|
def get_consensus(self):
|
||
|
"""Fetch a new consensus."""
|
||
|
|
||
|
# We're going to want a new consensus from our guard. In order
|
||
|
# to get that, we'll need a channel to our guard. In order to
|
||
|
# get that, we'll need a guard address. In order to get that,
|
||
|
# we'll need a consensus (uh, oh; in that case, fetch the
|
||
|
# consensus from a fallback relay).
|
||
|
|
||
|
|
||
|
guardaddr = self.channelmgr.guardaddr
|
||
|
guardchannel = None
|
||
|
if guardaddr is not None:
|
||
|
try:
|
||
|
guardchannel = self.channelmgr.get_channel_to(guardaddr)
|
||
|
except network.NetNoServer:
|
||
|
guardaddr = None
|
||
|
|
||
|
if guardchannel is None:
|
||
|
logging.debug("In bootstrapping mode")
|
||
|
self.channelmgr.get_consensus_from_fallbackrelay()
|
||
|
logging.debug('client consensus=%s', self.channelmgr.consensus)
|
||
|
return
|
||
|
|
||
|
if network.thenetwork.womode == network.WOMode.VANILLA:
|
||
|
if self.channelmgr.consensus is not None and len(self.channelmgr.consensus.consdict['relays']) > 0:
|
||
|
guardchannel.send_msg(simmsg.RelayGetConsensusDiffMsg())
|
||
|
logging.debug('got consensus diff, client consensus=%s', self.channelmgr.consensus)
|
||
|
return
|
||
|
|
||
|
# At this point, we are in one of the following scenarios:
|
||
|
# 1. This is a walking onions protocol, and the client fetches the
|
||
|
# complete consensus each epoch
|
||
|
# 2. This is Vanilla Onion Routing and the client doesn't have a
|
||
|
# consensus and needs to bootstrap it.
|
||
|
|
||
|
guardchannel.send_msg(simmsg.RelayGetConsensusMsg())
|
||
|
logging.debug('client consensus=%s', self.channelmgr.consensus)
|
||
|
|
||
|
def newepoch(self, epoch):
|
||
|
"""Callback that fires at the start of each epoch"""
|
||
|
|
||
|
# We'll need a new consensus
|
||
|
self.get_consensus()
|
||
|
|
||
|
# If we don't have a guard, pick one and make a channel to it
|
||
|
self.channelmgr.ensure_guard()
|