# rain: a BitTorrent client and library where every torrent gets its own peer port

> rain is a Go BitTorrent client that separates the server from the client in a single binary, speaks the core protocol plus a specific list of BEPs, and allocates a separate peer port per torrent for per account ratio tracking. The author states it is developed primarily to serve put.io, and the missing features list is deliberate rather than a roadmap.

**cenkalti/rain** — 🌧 BitTorrent client and library in Go

- Repository: https://github.com/cenkalti/rain
- Stars: 1,148 · Forks: 84
- Language: Go
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/cenkalti-rain

## The client-server split in one binary

rain operates as a client-server application with a single binary, and that shape is the first decision to understand. The server holds the session, the trackers, the DHT and the piece data. The client is a command line front end that talks to it.

The separation shows up immediately in the commands. The server is started with:

```sh
rain server
```

and everything else is a client subcommand. A torrent is added with rain client add, given either a magnet link, a path to a .torrent file, or a URL that points at one. Progress is inspected with rain client list, single torrent statistics with rain client stats, and the interactive console with rain client console. Control uses rain client stop, rain client start and rain client remove, each taking an --id.

That design has a consequence worth being explicit about. Because the client is a separate process talking to a running server, a client command only works when the server is up, and anything you want to automate has to be expressed as a client invocation or as a JSON-RPC call. The README lists a JSON-RPC 2.0 server and client among the client features, and rainrpc/ holds the shared types for that API, so the same operations are available to a remote caller without going through the CLI.

The project describes itself as a high-performance BitTorrent client and library in Go, running in production at put.io since 2019 and processing thousands of torrents daily with minimal system resources. The word library matters as much as client, and the same repository exposes the whole thing as a Go package.

The Go module path is github.com/cenkalti/rain/v2, so the major version is part of the import path. The repository also carries a CHANGELOG.md, a .goreleaser.yml for release binaries, and a .golangci.yml for linting, and the licence is MIT.

## Installing on macOS, from source, or from the releases page

There are three documented installation routes, and they are short.

On macOS, Homebrew:

```sh
brew install cenkalti/rain/rain
```

On other platforms, the README says to download the latest binary from the releases page. The release tags follow semantic versioning, with v2.4.3 dated 2026-10-10, v2.4.2 on 2026-10-03 and v2.4.1 on 2026-09-21, so patch-level updates arrive regularly and a pinned binary is easy to hold onto.

From source, with the Go toolchain:

```sh
go install github.com/cenkalti/rain/v2@latest
```

The go.mod file declares go 1.26.0, and the repository carries a .go-version file, so the toolchain expectation is explicit. The @latest form installs the newest tagged release rather than the head of master, which is the right choice for a binary you are going to run as a service.

A first run is two steps. Start the server with the default configuration, then add something to it:

```sh
rain server
rain client add "magnet:?xt=urn:btih:..."
```

The client add subcommand accepts the three input forms, a magnet link, a local .torrent path, or a URL:

```sh
rain client add /path/to/file.torrent
rain client add "https://example.com/file.torrent"
```

And rain help or rain help <command> gives the detail for any of them. There is also a JSON-RPC 2.0 server and client for remote management, plus an interactive console UI, which the README lists among the client features, and tools for creating and reading .torrent files.

## Embedding the session in a Go program

The library side is the same engine without the CLI, and the README shows the shape in one snippet. You create a session with the default config, add a magnet link, and then read stats off the torrent object:

```go
import "github.com/cenkalti/rain/v2/torrent"

// Create session with default config
ses, _ := torrent.NewSession(torrent.DefaultConfig)
defer ses.Close()

// Add a magnet link
magnetLink := "magnet:?xt=urn:btih:..."
tor, _ := ses.AddURI(magnetLink, nil)

// Monitor download progress
for range time.Tick(time.Second) {
	s := tor.Stats()
	log.Printf("Status: %s, Downloaded: %d, Peers: %d", s.Status.String(), s.Bytes.Completed, s.Peers.Total)
}
```

The import path carries the v2 segment, matching the module path. The nil second argument to AddURI is the options struct, so the defaults apply, and a complete example is said to exist under the handleDownload function in main.go, which is in the repository.

The error handling in the snippet is bare, with errors discarded on both calls, which is fine for illustration and wrong for production code. The point to note is that the session owns resources and has a Close method, so the lifecycle is explicit and the defer is not optional in a long running program.

The configuration surface for the library is the same one the server uses. The default config location is ~/rain/config.yaml, defaults are used if the file is missing, and a different file can be passed with rain server --config /path/to/config.yaml. The complete reference for every option is in torrent/config.go, and the README flags one detail that will bite anyone upgrading from an older release: configuration keys use the dash-separated format, for example port-begin rather than portbegin, starting from v2. A config file written for v1 will not read the same way in v2.

## A separate peer port for every torrent, and why that matters

The design decision that distinguishes rain from other BitTorrent clients is stated plainly in the architecture section. Unlike other BitTorrent clients, rain allocates a separate peer port for each torrent. The README gives three reasons this enables.

Multiple downloads of the same torrent for different accounts on private trackers. If two users of a private tracker are downloading the same file, sharing a peer port makes the tracker see the same peer identity for both, and the ratio accounting becomes wrong. With a separate port per torrent, each download is distinguishable.

Accurate ratio tracking per account. This is the same problem from the accounting side, and it is the reason the project exists in its current shape. The author is explicit that rain is primarily developed to serve the specific requirements of put.io, and the port design is a direct consequence of running a commercial seeding operation where per user upload and download ratios decide whether an account keeps its place.

Better isolation between torrents. Separate ports mean separate socket state per torrent, so a stalled or misbehaving transfer does not share resources with the others.

The cost is real. One peer port per torrent means one listening socket per torrent, which does not scale to a desktop user with a few downloads but is manageable when the process is a server built for hundreds of concurrent torrents. The README states the server-first goal directly: optimized for hundreds of concurrent torrents, minimal system resource usage, and a JSON-RPC API for remote management.

The rest of the architecture is layered. The core layer has torrent/session.go as the main orchestrator managing torrents, DHT and shared resources, and torrent/torrent.go for individual torrent lifecycle management. The protocol layer holds the BitTorrent protocol implementation, tracker communication and announcer, and DHT and PEX support. The network layer covers connection management, message stream encryption and resource limiting. The storage layer handles piece management, a piece cache, file I/O optimization, and resume data persistence with BoltDB.

The top level directory listing matches that split, with torrent/, rainrpc/, internal/ and main.go.

## Which BEPs are implemented and which are deliberately absent

Protocol support is listed explicitly, and it is a specific list rather than a vague claim. rain supports BEP-003 the core protocol, BEP-006 the fast extension, BEP-009 magnet links, BEP-012 multiple trackers, BEP-015 UDP trackers, BEP-005 DHT, BEP-011 PEX, BEP-019 WebSeed, and message stream encryption.

What is missing is stated with equal precision, and the framing matters. The README says these features are not implemented in rain, and that the list should not be considered a roadmap, because the features are intentionally omitted either because they do not provide sufficient value for the use case at put.io, or because implementing them would add unnecessary complexity to the codebase. The stated preference is to maintain a focused and efficient implementation that serves specific needs.

The omissions are: BEP-007 the IPv6 tracker extension, BEP-032 the IPv6 extension for DHT, BEP-029 the uTorrent transport protocol, BEP-016 superseeding, BEP-017 HTTP seeding, BEP-030 the Merkle tree torrent extension, uPnP port forwarding, and selective downloading.

Three of those have practical consequences. Without BEP-007 and BEP-032, a tracker that is IPv6 only, or a DHT that expects IPv6 peers, is a problem. Without uPnP port forwarding, a client behind NAT has to rely on the peer exchange and tracker mechanisms to discover its own address, which is workable but less reliable than a client that opens its own port. Without selective downloading, you download whole files, which is the single most commonly missed feature in a desktop client.

The contributing section is unusually direct about what happens to requests. It says rain is primarily developed to serve the specific requirements of put.io, that feature requests which do not align with those needs will likely be rejected, and that the maintainer runs a short list of projects. That is a governance statement more candid than most, and it tells you the right way to think about the project: it is an upstream component of a commercial product, published under MIT, and the pull requests accepted will be the ones that serve that product.

## Dependency choices that hint at what the code prioritises

The go.mod file is a reasonable place to look for the engineering priorities, since a Go dependency list is a statement about what you do not want to write yourself.

For data handling, github.com/zeebo/bencode handles the bencoded format that .torrent files use, and github.com/jackpal/bencode-go appears as an indirect dependency, so bencoding is well served. github.com/google/btree and go.etcd.io/bbolt give you the BoltDB store used for resume data, with a bbolt browser tool included for inspection, and github.com/otiai10/copy handles file copying.

For the protocol edges, github.com/nictuku/dht provides the DHT implementation and nettools as a companion. github.com/powerman/rpc-codec is the JSON-RPC 2.0 codec behind the API, and github.com/rcrowley/go-metrics gives you metrics, which fits a project that talks about resource management and running in production.

For the interface, github.com/jroimartin/gocui and the termbox libraries underneath it drive the interactive console UI, and github.com/urfave/cli is the command line framework behind the rain client subcommands. github.com/cenkalti/log is the logging library, and cenkalti/backoff/v7 handles retry backoff, which is what a tracker announcer needs. github.com/juju/ratelimit is a rate limiter, consistent with a project that talks about resource management for connections, memory and file handles.

The test dependencies are worth a look too. github.com/stretchr/testify is the assertion library, and go.uber.org/goleak is in the direct require list, which means the test suite checks for goroutine leaks. For a program that holds long lived peer connections and trackers, a leak check is a meaningful guard, and having it as a direct dependency rather than a transitive one suggests it is used deliberately.

The module also requires golang.org/x/sync and golang.org/x/sys, gopkg.in/yaml.v2 for the configuration file format, and github.com/fatih/structs, which is a reflection helper typically used to map a struct to a map when serialising configuration. The configuration file is YAML and the config struct is reflected into it, which is how the dash-separated key naming gets generated.

## Conclusion

rain fits a Go team that needs a BitTorrent engine as a library or as a headless client under its own control, especially one running a private tracker with per account ratio tracking, since the separate port per torrent design exists for that case. It is the wrong tool for a desktop user who wants a general purpose torrent client, because selective downloading, IPv6 tracker support, HTTP seeding and uPnP port forwarding are all listed as not implemented. Before adopting, read the missing features list in full and decide whether your tracker and your peers need IPv6, then read torrent/config.go for the configuration surface, since the YAML keys are dash separated from v2 and the file is the only complete reference.

## FAQ

### How do I install rain and add my first torrent?

On macOS use brew install cenkalti/rain/rain, on other platforms download a binary from the releases page, or install from source with go install github.com/cenkalti/rain/v2@latest. Then start rain server and use rain client add with a magnet link, a .torrent file path, or a URL.

### Why does rain allocate a separate peer port for each torrent?

The README explains it enables multiple downloads of the same torrent for different accounts on private trackers, accurate ratio tracking per account, and better isolation between torrents. The design targets servers running hundreds of concurrent torrents rather than desktop use.

### What does rain not support?

The missing features list names the IPv6 tracker extension BEP-007, the IPv6 DHT extension BEP-032, the uTorrent transport protocol BEP-029, superseeding BEP-016, HTTP seeding BEP-017, the Merkle tree extension BEP-030, uPnP port forwarding and selective downloading. The README says this is not a roadmap but a deliberate choice to keep the implementation focused.

### How do I configure rain?

Rain reads a YAML configuration file, by default at ~/rain/config.yaml, and uses defaults when the file is missing. A different path is given with rain server --config /path/to/config.yaml, the full option list is in torrent/config.go, and from v2 the keys are dash separated, for example port-begin.

## Sources

- [cenkalti/rain on GitHub](https://github.com/cenkalti/rain)
- [Issues](https://github.com/cenkalti/rain/issues)
- [License: MIT](https://github.com/cenkalti/rain/blob/master/LICENSE)
- [README](https://github.com/cenkalti/rain/blob/master/README.md)
- [Releases](https://github.com/cenkalti/rain/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cenkalti-rain
