commit b4027b16fc102165f6a29e58bc09d1d7631f21cc Author: teor teor2345@gmail.com Date: Wed Oct 1 22:24:04 2014 +1000
Stop using unsupported torrc options
When writing a torrc file, check if the selected tor binary supports the option in each line we're writing out, using tor --list-torrc-options. If it doesn't, disable the option line by commenting it out. (And add a note "unsupported" to avoid confusion.) --- lib/chutney/TorNet.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 3 deletions(-)
diff --git a/lib/chutney/TorNet.py b/lib/chutney/TorNet.py index 6a1fc8b..e4ddc7a 100644 --- a/lib/chutney/TorNet.py +++ b/lib/chutney/TorNet.py @@ -25,6 +25,7 @@ import chutney.Templating import chutney.Traffic
_BASE_ENVIRON = None +_TORRC_OPTIONS = None _THE_NETWORK = None
@@ -213,8 +214,10 @@ class LocalNodeBuilder(NodeBuilder): self._env = env
def _createTorrcFile(self, checkOnly=False): - """Write the torrc file for this node. If checkOnly, just make sure - that the formatting is indeed possible. + """Write the torrc file for this node, disabling any options + that are not supported by env's tor binary using comments. + If checkOnly, just make sure that the formatting is indeed + possible. """ fn_out = self._getTorrcFname() torrc_template = self._getTorrcTemplate() @@ -222,8 +225,66 @@ class LocalNodeBuilder(NodeBuilder): if checkOnly: # XXXX Is it time-cosuming to format? If so, cache here. return + # now filter the options we're about to write, commenting out + # the options that the current tor binary doesn't support + tor = self._env['tor'] + # find the options the current tor binary supports, and cache them + if tor not in _TORRC_OPTIONS: + # Note: some versions of tor (e.g. 0.2.4.23) require + # --list-torrc-options to be the first argument + cmdline = [ + tor, + "--list-torrc-options", + "--hush"] + try: + opts = subprocess.check_output(cmdline, + bufsize=-1, + universal_newlines=True) + except OSError as e: + # only catch file not found error + if e.errno == errno.ENOENT: + print ("Cannot find tor binary %r. Use " + "CHUTNEY_TOR environment variable to set the " + "path, or put the binary into $PATH.") % tor + sys.exit(0) + else: + raise + # check we received a list of options, and nothing else + assert re.match(r'(^\w+$)+', opts, flags=re.MULTILINE) + torrc_opts = opts.split() + # cache the options for this tor binary's path + _TORRC_OPTIONS[tor] = torrc_opts + else: + torrc_opts = _TORRC_OPTIONS[tor] + # check if each option is supported before writing it + # TODO: what about unsupported values? + # e.g. tor 0.2.4.23 doesn't support TestingV3AuthInitialVoteDelay 2 + # but later version do. I say throw this one to the user. with open(fn_out, 'w') as f: - f.write(output) + # we need to do case-insensitive option comparison + # even if this is a static whitelist, + # so we convert to lowercase as close to the loop as possible + lower_opts = [opt.lower() for opt in torrc_opts] + # keep ends when splitting lines, so we can write them out + # using writelines() without messing around with "\n"s + for line in output.splitlines(True): + # check if the first word on the line is a supported option, + # preserving empty lines and comment lines + sline = line.strip() + if (len(sline) == 0 + or sline[0] == '#' + or sline.split()[0].lower() in lower_opts): + f.writelines([line]) + else: + # well, this could get spammy + # TODO: warn once per option per tor binary + # TODO: print tor version? + print ("The tor binary at %r does not support the " + "option in the torrc line:\n" + "%r") % (tor, line.strip()) + # we could decide to skip these lines entirely + # TODO: write tor version? + f.writelines(["# " + tor + " unsupported: " + line])
def _getTorrcTemplate(self): """Return the template used to write the torrc for this node.""" @@ -757,8 +818,13 @@ def runConfigFile(verb, f):
def main(): global _BASE_ENVIRON + global _TORRC_OPTIONS global _THE_NETWORK _BASE_ENVIRON = TorEnviron(chutney.Templating.Environ(**DEFAULTS)) + # _TORRC_OPTIONS gets initialised on demand as a map of + # "/path/to/tor" => ["SupportedOption1", "SupportedOption2", ...] + # Or it can be pre-populated as a static whitelist of options + _TORRC_OPTIONS = dict() _THE_NETWORK = Network(_BASE_ENVIRON)
if len(sys.argv) < 3:
tor-commits@lists.torproject.org