Hello there! This is the fourth in a series of blogposts detailing the work I will be doing for the Tor Project as part of Google Summer of Code 2023.
Here I will detail some of the work I did during some period of time, the challenges I faced and the outcomes of that work.
The project I was selected for is titled "Arti API exploration to build example tools".
Confused? Let's break it down.
Arti is the Rust rewrite of the Tor software that allows you to make TCP connections through the Tor Network, run Tor relays and bridges etc. You can read more about it from its repository
Arti was built in mind with the goal that other developers should be able to use Arti to build Tor-powered software, and has incorporated that thinking into its design.
So, it exports some crates that other developers can use in their Rust projects, and has some documentation on them, including some basic demonstration code snippets that you can follow along with at home.
However, Arti is a fairly bleeding-edge project. It didn't hit version 1.0.0 too long ago, and due to the breakneck speed of development, APIs are not set in stone. There is a lot of breakage that could be potentially encountered by another developer.
In this project, I will be creating certain sample programs in Rust using Arti's APIs
My goal will be to build my sample programs and document any difficulties that come up.
In this way, the project can get valuable feedback from an outsider who doesn't have much knowledge of the codebase and the way the Arti proxy does things.
I worked on the notes I'd gotten on the DNS
resolver this week, mainly to document everything. During this
process I mainly got used to the rustdocs convention
of documenting my code and also setup a clippy rule to warn me in
case any method had missing documentation.
I ended up documenting structs, their fields and a couple functions, as well as adding some introductory text detailing the DNS resolver project's objective and a warning that this homegrown DNS implementation really wasn't meant to be used anywhere in production, but rather served as a demonstration that any homegrown network protocol could be subbed in and served over Tor using the Arti APIs.
This was a fun exercise, and I will expand the other projects' documentation in the coming weeks as well.
In the DNS resolver, there is a trait called
FromBytes that is implemented for the structs
Header (which is the DNS header represented as a
struct), Query (which is the DNS header + query),
ResourceRecord (which can be thought of as containing
the answer for a query) and Response (which is just
the header + query + one or more resource records).
This FromBytes trait defines a method
from_bytes() which takes in a slice of bytes and
returns the initialized struct. As the name implies, all the values
for the fields will be taken from the bytes passed to this
method.
Now, the problem with this method was that it passes the struct no matter what. Even if we give invalid byte sequence to the method, we will get a struct. This is highly dangerous since we don't have any way to handle invalid byte sequences or convey this message to functions lower down the call stack.
Hence, I decided to change the return type from
Self (the struct itself) to
Option<Self> (Option is an enum which either
returns Some<Self> or None, hence
we can denote the error condition using None and a
valid struct using Some).
However, since this is a trait, Self can be a
variable sized struct, and Rust doesn't like
that. So, we wrap it inside a Box (really just a very
simple pointer setup, the Box will contain a reference
to wherever the struct is located on the heap so we can pass the
Box around easily and cheaply and also be aware of its
size)
Hence the final return type becomes
Option<Box<Self>> which is a cleaner API
than just returning Self This also has the nice side
effect that the DNS resolver doesn't crash when
I make a request for a domain name that isn't
registered!
For this week, I intended to work on adding resume capabilities for the download manager, ie, if I were to abruptly cut off network connection and later restart the download manager, I should be able to pick up my download where I left off.
However, I did notice a feature that was in a way more important: retrying requests. See, when I configured the download manager to use Snowflake to download Tor Browser, I would see a lot of failed circuits and timeouts. This also happened when I used a normal Tor connection, but to a much lesser extent.
So, to address this pitfall, I simply decided I wanted to check if we got anything back from the network and if not, just re-run the code that makes the request using recursion.
However, Rust doesn't permit asynchronous recursive functions, likely because it will make the call stack very weird, so I ended up just doing it iteratively instead. The code now tries up to six times to make the same request and if it doesn't work after all that, it just gives up.
This has made the download manager much more robust, as I do see that on occasion one or two requests do indeed trigger a retry.
While working on the obfs4 connection checker, it was suggested
that ChanMgr not be used, and instead a lower level
API would be more suitable, since ChanMgr does some
setup steps that are just irrelevant for this use case.
The ChannelFactory trait in
tor-chanmgr defines an interface through which I can
build a channel. Exposing this in ChanMgr will allow
me to directly create a Channel. This API is exposed in
this MR and is currently not merged.
This week was also fairly eventful, I ended up working on a lot of the review notes I got on the DNS resolver, fix up download manager, learn and work around the Rust compiler's rules to implement better design practices in my programs and made another MR upstream.
This website was made using Markdown, Pandoc, and a custom program to automatically add headers and footers (including this one) to any document that's published here.
Copyright © 2026 Saksham Mittal. All rights reserved. Unless otherwise stated, all content on this website is licensed under the CC BY-SA 4.0 International License