libtorrent Agent Skills & AI Rules

GitHub

An efficient feature complete C++ bittorrent implementation

5,993 stars C++ C++ 1 file ยท ~27,021 tokens #bittorrent#cpp#library
RAW Rules
5 Discovered Rules โ€ข ~10,945 Estimated Tokens
Quick:
Loading skill content...

Rules Skill

.claude/rules/dht.md anthropic-skill ~3,301 tokens
Raw
---
paths:
  - "src/kademlia/**"
  - "include/libtorrent/kademlia/**"
---

# DHT (Kademlia) Implementation

Source: `src/kademlia/`, `include/libtorrent/kademlia/`

## BEP Support

| BEP | Description | Status |
|-----|-------------|--------|
| BEP 5  | DHT Protocol (ping, find_node, get_peers, announce_peer) | Full |
| BEP 32 | IPv6 extension (dual-stack, nodes6) | Full |
| BEP 33 | DHT Scrape (bloom filters BFpe/BFsd) | Full |
| BEP 42 | Node ID Verification (IP-derived IDs) | Full |
| BEP 44 | Arbitrary data storage (immutable + mutable items, Ed25519) | Full |
| BEP 51 | DHT Infohash Indexing (sample_infohashes) | Full |

---

## File Overview

| File | Purpose |
|------|---------|
| `node.cpp` / `node.hpp` | Central DHT node โ€” routing, dispatch, traversal lifecycle |
| `dht_tracker.cpp` / `dht_tracker.hpp` | Session-level coordinator, one `node` per listen socket |
| `routing_table.cpp` / `routing_table.hpp` | K-bucket routing table |
| `rpc_manager.cpp` / `rpc_manager.hpp` | Tracks outstanding RPCs, calls observers on response/timeout |
| `observer.hpp` | Base observer class for pending RPC state |
| `traversal_algorithm.cpp` / `.hpp` | Base iterative search algorithm |
| `find_data.cpp` / `.hpp` | Find-nodes traversal, base for get_peers and get_item |
| `get_peers.cpp` / `.hpp` | BEP 5 peer lookup traversal |
| `get_item.cpp` / `.hpp` | BEP 44 item retrieval traversal |
| `put_data.cpp` / `.hpp` | BEP 44 item storage traversal |
| `sample_infohashes.cpp` / `.hpp` | BEP 51 traversal |
| `refresh.cpp` / `.hpp` | Bootstrap / bucket-refresh traversal |
| `direct_request.hpp` | Single-message direct request |
| `node_id.cpp` / `node_id.hpp` | `node_id` type (alias for `sha1_hash`), XOR distance, BEP 42 generation |
| `node_entry.cpp` / `node_entry.hpp` | Single routing-table entry |
| `item.cpp` / `item.hpp` | Mutable/immutable item type |
| `types.hpp` | `public_key`, `secret_key`, `signature`, `sequence_number` |
| `msg.hpp` | Incoming message structure |
| `dht_storage.cpp` / `dht_storage.hpp` | Pure-virtual storage interface + default RAM implementation |
| `dht_state.cpp` / `dht_state.hpp` | Persistent state (node IDs, bootstrap nodes) |
| `dht_settings.hpp` | Per-DHT settings struct |
| `dht_observer.hpp` | Callbacks from DHT into `session_impl` |
| `dos_blocker.cpp` / `dos_blocker.hpp` | Per-IP rate limiting (5 pkt/s, 5-min block) |
| `ed25519.hpp` | Ed25519 sign/verify API (vendored impl in `src/ed25519/`) |
| `announce_flags.hpp` | Announce flags: `seed`, `implied_port`, `ssl_torrent` |

---

## Threading

All DHT code runs on the **session network thread** (boost.asio). No locking needed inside DHT classes. `rpc_manager` uses a mutex only for its observer-pool allocator.

---

## Core Data Structures

### `node_id`
Type alias for `sha1_hash` (160 bits).

Key functions:
- `distance(a, b)` โ€” XOR metric
- `distance_exp(a, b)` โ€” index of highest differing bit (0โ€“160)
- `compare_ref(a, b, ref)` โ€” compare distances to reference
- `generate_id(external_ip)` โ€” BEP 42 derivation
- `verify_id(nid, source_ip)` โ€” BEP 42 check

### `node_entry`
One routing-table peer entry:
- `node_id id`, `udp::endpoint endpoint`
- `uint16_t rtt` (0xffff = unknown)
- `uint8_t timeout_count` (0 = confirmed, 0xff = never pinged)
- `bool verified` (BEP 42)
- `time_point last_queried`

### `routing_table`
160 k-buckets (one per XOR distance bit). Extended routing table (default on) gives larger buckets close to us:

| Bucket index | Multiplier | Max live nodes (k=20) |
|---|---|---|
| 0 | 16ร— | 320 |
| 1 | 8ร— | 160 |
| 2 | 4ร— | 80 |
| 3 | 2ร— | 40 |
| 4โ€“159 | 1ร— | 20 (k) |

Key behaviours:
- Last (closest) bucket splits when it exceeds k entries
- Each bucket has a **replacement cache**; candidates promoted when live node is removed
- `ip_set` restricts one node per IP per bucket (BEP 42, `restrict_routing_ips`)
- Periodic refresh: random query in each bucket's range every ~15 min

### `item` (BEP 44)
- **Immutable** โ€” key is `sha1(value)`, no signature
- **Mutable** โ€” key is `sha1(salt || pk)`, Ed25519-signed, has `sequence_number`
- CAS: `cas` field lets clients do compare-and-swap on sequence number

### `dht_storage_interface`
Pure-virtual; default RAM implementation (`dht_default_storage_constructor`):

```
map<sha1_hash, torrent_entry>        m_torrents       (BEP 5 peers)
map<sha1_hash, dht_immutable_item>   m_immutable_items
map<sha1_hash, dht_mutable_item>     m_mutable_items
```

Eviction uses distance from our node IDs โ€” items far from us are evicted first.
Limits: `max_torrents` (2000), `max_peers` (500), `max_dht_items` (700).
Peer entries expire after 30 min (announce_interval).

---

## Key Classes

### `node`
One per listen socket. Owns `routing_table` and `rpc_manager`.

Important methods:
```cpp
void bootstrap(endpoints, done_cb);
void get_peers(info_hash, callback, flags);
void announce(info_hash, port, flags, callback);
void get_item(target, callback);           // immutable
void get_item(pk, salt, callback);         // mutable
void put_item(data, callback);             // immutable
void put_item(pk, sk, salt, callback);     // mutable
void sample_infohashes(endpoint, target, callback);
void incoming(udp::endpoint, msg);         // dispatch received UDP
void tick();                               // token rotation, bucket refresh
```

Write tokens: `hash(requester_ip + secret + info_hash)`. Two secrets kept; rotated periodically to allow stale tokens for one rotation period.

### `dht_tracker`
Owns `std::map<listen_socket_handle, tracker_node>` (one `node` per socket).
Aggregates results across all nodes. Manages DOS blocking. Saves/restores `dht_state`.

### `rpc_manager`
- `invoke(entry, endpoint, observer_ptr)` โ€” sends bencoded RPC, tracks by 2-byte transaction ID
- `incoming(msg, &sender_id)` โ€” looks up observer by txn-id, calls `observer::reply()`
- `tick()` โ€” fires `observer::timeout()` / `observer::short_timeout()` for stale requests
- Uses an object pool for observers to reduce heap fragmentation

### `observer`
One per pending RPC. Flags:

| Flag | Meaning |
|------|---------|
| `flag_queried` | Request sent |
| `flag_initial` | First attempt |
| `flag_short_timeout` | Intermediate timeout fired |
| `flag_failed` | Node failed |
| `flag_alive` | Response received |
| `flag_done` | Finished, no longer tracked |

Observer hierarchy:
```
observer
  โ”œโ”€ traversal_observer          (parses "nodes"/"nodes6" from responses)
  โ”‚   โ”œโ”€ find_data_observer
  โ”‚   โ”‚   โ”œโ”€ get_peers_observer  (also parses "values", bloom filters)
  โ”‚   โ”‚   โ””โ”€ get_item_observer   (parses v/k/sig/seq)
  โ”‚   โ”œโ”€ put_data_observer
  โ”‚   โ””โ”€ sample_infohashes_observer
  โ”œโ”€ announce_observer
  โ”œโ”€ direct_observer
  โ””โ”€ null_observer
```

---

## Traversal Algorithms

All searches inherit from `traversal_algorithm`. The base implements the Kademlia iterative lookup:

1. Seed result set with k closest nodes from routing table
2. Issue ฮฑ (default 3) concurrent requests (`add_requests()`)
3. Each response: add new closer nodes, sort result set
4. Repeat until k closest nodes all responded or timeout
5. Call `done()`

| Class | BEP | RPC sent | Callback delivers |
|-------|-----|----------|-------------------|
| `find_data` | 5 | `find_node` | closest nodes + write tokens |
| `get_peers` | 5 | `get_peers` | `vector<tcp::endpoint>` (peers) |
| `get_item` | 44 | `get` | `item`, `bool authoritative` |
| `put_data` | 44 | first `get`, then `put` | response count |
| `sample_infohashes` | 51 | `sample_infohashes` | samples, interval, total |
| `bootstrap` / `refresh` | 5 | `get_peers` (our own ID) | (fires bucket refresh) |
| `direct_traversal` | โ€” | arbitrary | single `msg` |

`obfuscated_get_peers`: wraps `get_peers`, uses a random target until convergence, then switches to the real info_hash (BEP 5 privacy mode, controlled by `dht_privacy_lookups`).

---

## Message Format (bencode)

All messages are bencoded dicts with mandatory keys `"t"` (transaction id), `"y"` (`"q"/"r"/"e"`), optional `"v"` (version), `"ip"` (external address, BEP 42), `"ro"` (read-only, BEP 5).

**Queries** (`y="q"`): `"q"` = method name, `"a"` = argument dict with `"id"` (our node id, 20 bytes).

**Responses** (`y="r"`): `"r"` = response dict.

**Errors** (`y="e"`): `"e"` = `[error_code, "message"]`.

### Method summary

| Method | Key request fields | Key response fields |
|--------|--------------------|---------------------|
| `ping` | `id` | `id` |
| `find_node` | `id`, `target`, `want` | `id`, `nodes`, `nodes6` |
| `get_peers` | `id`, `info_hash`, `noseed`, `scrape`, `want` | `id`, `token`, `values`\|`nodes`, `BFpe`, `BFsd` |
| `announce_peer` | `id`, `info_hash`, `port`, `token`, `implied_port`, `seed` | `id` |
| `get` (BEP 44) | `id`, `target`, [`seq`] | `id`, `v`, [`k`, `sig`, `seq`] |
| `put` (BEP 44) | `id`, `v`, [`k`, `sig`, `seq`, `salt`, `cas`] | `id` |
| `sample_infohashes` | `id`, `target` | `id`, `samples`, `num`, `interval`, `nodes` |

Compact node encoding: 26 bytes per IPv4 node (20-byte id + 4-byte IP + 2-byte port), 38 bytes for IPv6.

---

## Bootstrap

1. Load `dht_state` (saved node IDs + endpoints)
2. Assign node ID: saved โ†’ BEP 42 from external IP โ†’ random
3. Add router nodes from settings (`router.bittorrent.com:6881`, etc.)
4. Run `bootstrap` traversal against saved/configured endpoints (queries `get_peers` for own ID)
5. On completion, refresh all non-contiguous buckets
6. `dht_observer::get_peers()` notifies `session_impl` to query for all active torrents

---

## Integration with `session_impl`

`session_impl` owns `shared_ptr<dht_tracker> m_dht` and implements `dht_observer`:

| Observer method | When called |
|-----------------|-------------|
| `set_external_address()` | DHT detects our external IP via `"ip"` field |
| `get_listen_port(socket)` | Node asks for our announce port |
| `get_peers(info_hash, peer)` | Another node announced a peer to us |
| `announce(info_hash, endpoint)` | We received a get_peers for one of our torrents |
| `on_dht_request(method, msg, reply)` | Custom extension message handler |

Session calls:
- `dht_tracker::incoming_packet(socket, endpoint, buffer)` โ€” for each received DHT UDP packet
- `dht_tracker::new_socket(handle)` / `delete_socket(handle)` โ€” listen port changes
- `dht_tracker::tick()` โ€” periodic maintenance

Relevant `settings_pack` keys:

| Setting | Effect |
|---------|--------|
| `enable_dht` | Toggle DHT |
| `dht_search_branching` | Alpha (concurrent requests) |
| `dht_max_peers_reply` | Peers returned per get_peers response |
| `dht_enforce_node_id` | Reject nodes failing BEP 42 check |
| `dht_restrict_routing_ips` | One node per /24 IPv4 per bucket |
| `dht_restrict_search_ips` | Sybil mitigation in traversals |
| `dht_privacy_lookups` | Enable obfuscated_get_peers |
| `dht_read_only` | Set `ro=1` flag, don't accept queries |
| `dht_upload_rate_limit` | Response bytes/sec cap |
| `dht_item_lifetime` | BEP 44 item expiry (0 = never) |
| `dht_sample_infohashes_interval` | BEP 51 cache TTL |

---

## Constants and Tunables

| Parameter | Value | Notes |
|-----------|-------|-------|
| Node ID | 160 bits | SHA-1 |
| k (bucket size) | 20 | Configurable |
| Alpha | 3 | Concurrent requests per traversal |
| RPC full timeout | 15 s | |
| RPC short timeout | ~6 s | Triggers next request early |
| Token rotation | ~7 min | Two tokens kept |
| Bucket refresh | 15 min | Per-bucket, measured from last query |
| Peer announce validity | 30 min | |
| DOS block rate | 5 pkt/s | Per source IP |
| DOS block duration | 5 min | |
| DOS tracker size | 20 | Most-recent offenders |