338 lines
14 KiB
C
338 lines
14 KiB
C
/*
|
|
* tracker.h - Protocol core for BitTorrent trackers.
|
|
*
|
|
* This library focuses on tracker-side parsing and response formatting. The
|
|
* ABI deliberately stays protocol-neutral: HTTP(S) and UDP announce/scrape
|
|
* inputs normalize into the same request structs, and servers can format
|
|
* compact IPv4/IPv6 peer responses from one endpoint list.
|
|
*/
|
|
#ifndef TORRENT_TRACKER_H
|
|
#define TORRENT_TRACKER_H
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define TRACKER_INFO_HASH_LEN 20u
|
|
#define TRACKER_PEER_ID_LEN 20u
|
|
#define TRACKER_MAX_PEERS 256u
|
|
#define TRACKER_MAX_SCRAPE 74u
|
|
#define TRACKER_MAX_URL_DATA 512u
|
|
#define DHT_NODE_ID_LEN 20u
|
|
#define DHT_MAX_TRANSACTION 16u
|
|
#define DHT_MAX_TOKEN 64u
|
|
#define DHT_MAX_NODES 256u
|
|
#define DHT_MAX_ERROR 128u
|
|
|
|
typedef enum {
|
|
TRACKER_OK = 0,
|
|
TRACKER_EINVAL = -1,
|
|
TRACKER_ETOOSMALL= -2,
|
|
TRACKER_ETOOBIG = -3,
|
|
TRACKER_ENOSPC = -4,
|
|
TRACKER_EPARSE = -5
|
|
} tracker_result;
|
|
|
|
typedef enum {
|
|
TRACKER_EVENT_NONE = 0,
|
|
TRACKER_EVENT_COMPLETED = 1,
|
|
TRACKER_EVENT_STARTED = 2,
|
|
TRACKER_EVENT_STOPPED = 3
|
|
} tracker_event;
|
|
|
|
typedef enum {
|
|
TRACKER_ADDR_IPV4 = 4,
|
|
TRACKER_ADDR_IPV6 = 6
|
|
} tracker_addr_family;
|
|
|
|
typedef struct {
|
|
uint8_t family; /* TRACKER_ADDR_IPV4 or TRACKER_ADDR_IPV6 */
|
|
uint8_t addr[16]; /* first 4 bytes used for IPv4 */
|
|
uint16_t port; /* host byte order */
|
|
uint8_t peer_id[20];
|
|
uint8_t has_peer_id;
|
|
} tracker_peer;
|
|
|
|
typedef struct {
|
|
uint8_t info_hash[20];
|
|
uint8_t peer_id[20];
|
|
uint16_t port;
|
|
uint64_t uploaded;
|
|
uint64_t downloaded;
|
|
uint64_t left;
|
|
int32_t numwant; /* -1 means default */
|
|
uint32_t key;
|
|
uint32_t ip4; /* host byte order, 0 means use source IP */
|
|
tracker_event event;
|
|
uint8_t compact;
|
|
uint8_t no_peer_id;
|
|
uint8_t has_key;
|
|
uint8_t has_ip4;
|
|
char ip[64]; /* HTTP ip= value, if supplied */
|
|
char tracker_id[128];
|
|
char url_data[TRACKER_MAX_URL_DATA]; /* BEP-41 UDP URLData */
|
|
} tracker_announce_request;
|
|
|
|
typedef struct {
|
|
uint32_t interval;
|
|
uint32_t min_interval;
|
|
uint32_t complete; /* seeders */
|
|
uint32_t incomplete; /* leechers */
|
|
const char *tracker_id;
|
|
const tracker_peer *peers;
|
|
size_t peer_count;
|
|
uint8_t compact; /* compact peers/peers6 response */
|
|
} tracker_announce_response;
|
|
|
|
typedef struct {
|
|
uint8_t info_hash[20];
|
|
uint32_t complete;
|
|
uint32_t downloaded;
|
|
uint32_t incomplete;
|
|
} tracker_scrape_file;
|
|
|
|
typedef struct {
|
|
const tracker_scrape_file *files;
|
|
size_t file_count;
|
|
} tracker_scrape_response;
|
|
|
|
typedef struct {
|
|
uint32_t interval; /* announce interval advertised to clients */
|
|
uint32_t min_interval; /* optional minimum announce interval */
|
|
uint32_t peer_timeout; /* expire peers older than this many seconds */
|
|
uint32_t default_numwant; /* used when request numwant is -1 */
|
|
uint32_t max_numwant; /* hard cap on returned peers */
|
|
uint64_t random_seed; /* 0 => deterministic default seed */
|
|
} tracker_store_config;
|
|
|
|
typedef struct tracker_store tracker_store;
|
|
|
|
tracker_store *tracker_store_create(const tracker_store_config *cfg);
|
|
void tracker_store_destroy(tracker_store *store);
|
|
|
|
/* Apply one announce to the in-memory swarm table and prepare a response.
|
|
* source_addr is the observed remote address; source_addr->port is ignored and
|
|
* req->port is advertised. Trackers should prefer this source address over
|
|
* user-supplied ip= values to avoid reflector abuse. out_peers is caller-owned
|
|
* response storage and is referenced by resp->peers on success. */
|
|
int tracker_store_announce(tracker_store *store,
|
|
const tracker_announce_request *req,
|
|
const tracker_peer *source_addr,
|
|
uint64_t now_sec,
|
|
tracker_peer *out_peers,
|
|
size_t out_peer_cap,
|
|
tracker_announce_response *resp);
|
|
|
|
int tracker_store_scrape(tracker_store *store,
|
|
const uint8_t hashes[][20],
|
|
size_t hash_count,
|
|
tracker_scrape_file *out_files,
|
|
size_t out_file_cap,
|
|
tracker_scrape_response *resp);
|
|
|
|
/* Remove peers that have not announced within cfg.peer_timeout seconds. */
|
|
size_t tracker_store_prune(tracker_store *store, uint64_t now_sec);
|
|
|
|
size_t tracker_store_swarm_count(const tracker_store *store);
|
|
size_t tracker_store_peer_count(const tracker_store *store);
|
|
|
|
typedef enum {
|
|
TRACKER_UDP_CONNECT = 0,
|
|
TRACKER_UDP_ANNOUNCE = 1,
|
|
TRACKER_UDP_SCRAPE = 2,
|
|
TRACKER_UDP_ERROR = 3
|
|
} tracker_udp_action;
|
|
|
|
typedef struct {
|
|
tracker_udp_action action;
|
|
uint32_t transaction_id;
|
|
uint64_t connection_id;
|
|
tracker_announce_request announce;
|
|
uint8_t scrape_hashes[TRACKER_MAX_SCRAPE][20];
|
|
size_t scrape_count;
|
|
} tracker_udp_request;
|
|
|
|
/* HTTP(S) tracker protocol. Query may be the raw query string or a full path
|
|
* containing '?'. info_hash and peer_id are percent-decoded and must be 20
|
|
* bytes for announce requests. */
|
|
int tracker_http_parse_announce_query(const char *query,
|
|
tracker_announce_request *out);
|
|
int tracker_http_parse_scrape_query(const char *query,
|
|
uint8_t hashes[][20], size_t max_hashes,
|
|
size_t *hash_count);
|
|
int tracker_http_write_announce_response(const tracker_announce_response *resp,
|
|
uint8_t *buf, size_t cap,
|
|
size_t *written);
|
|
int tracker_http_write_scrape_response(const tracker_scrape_response *resp,
|
|
uint8_t *buf, size_t cap,
|
|
size_t *written);
|
|
int tracker_http_write_failure(const char *message, uint8_t *buf, size_t cap,
|
|
size_t *written);
|
|
|
|
/* Client-side HTTP helpers. Query writers produce the path query component
|
|
* without a leading '?'. Response parsers accept a raw bencoded tracker body
|
|
* and fill caller-owned peer/file arrays. */
|
|
int tracker_http_write_announce_query(const tracker_announce_request *req,
|
|
char *buf, size_t cap,
|
|
size_t *written);
|
|
int tracker_http_write_scrape_query(const uint8_t hashes[][20],
|
|
size_t hash_count,
|
|
char *buf, size_t cap,
|
|
size_t *written);
|
|
int tracker_http_parse_announce_response(const uint8_t *buf, size_t len,
|
|
tracker_peer *out_peers,
|
|
size_t out_peer_cap,
|
|
tracker_announce_response *resp);
|
|
int tracker_http_parse_scrape_response(const uint8_t *buf, size_t len,
|
|
tracker_scrape_file *out_files,
|
|
size_t out_file_cap,
|
|
tracker_scrape_response *resp);
|
|
|
|
/* UDP tracker protocol (BEP-15 plus BEP-41 URLData parsing on announces). */
|
|
int tracker_udp_parse_request(const uint8_t *packet, size_t len,
|
|
tracker_addr_family source_family,
|
|
tracker_udp_request *out);
|
|
int tracker_udp_write_connect_response(uint32_t transaction_id,
|
|
uint64_t connection_id,
|
|
uint8_t *buf, size_t cap,
|
|
size_t *written);
|
|
int tracker_udp_write_announce_response(uint32_t transaction_id,
|
|
tracker_addr_family family,
|
|
const tracker_announce_response *resp,
|
|
uint8_t *buf, size_t cap,
|
|
size_t *written);
|
|
int tracker_udp_write_scrape_response(uint32_t transaction_id,
|
|
const tracker_scrape_response *resp,
|
|
uint8_t *buf, size_t cap,
|
|
size_t *written);
|
|
int tracker_udp_write_error(uint32_t transaction_id, const char *message,
|
|
uint8_t *buf, size_t cap, size_t *written);
|
|
|
|
/* Client-side UDP helpers. */
|
|
int tracker_udp_write_connect_request(uint32_t transaction_id,
|
|
uint8_t *buf, size_t cap,
|
|
size_t *written);
|
|
int tracker_udp_parse_connect_response(const uint8_t *packet, size_t len,
|
|
uint32_t transaction_id,
|
|
uint64_t *connection_id);
|
|
int tracker_udp_write_announce_request(uint64_t connection_id,
|
|
uint32_t transaction_id,
|
|
const tracker_announce_request *req,
|
|
uint8_t *buf, size_t cap,
|
|
size_t *written);
|
|
int tracker_udp_parse_announce_response(const uint8_t *packet, size_t len,
|
|
uint32_t transaction_id,
|
|
tracker_addr_family family,
|
|
tracker_peer *out_peers,
|
|
size_t out_peer_cap,
|
|
tracker_announce_response *resp);
|
|
int tracker_udp_write_scrape_request(uint64_t connection_id,
|
|
uint32_t transaction_id,
|
|
const uint8_t hashes[][20],
|
|
size_t hash_count,
|
|
uint8_t *buf, size_t cap,
|
|
size_t *written);
|
|
int tracker_udp_parse_scrape_response(const uint8_t *packet, size_t len,
|
|
uint32_t transaction_id,
|
|
tracker_scrape_file *out_files,
|
|
size_t out_file_cap,
|
|
tracker_scrape_response *resp);
|
|
|
|
typedef enum {
|
|
DHT_MSG_QUERY = 1,
|
|
DHT_MSG_RESPONSE = 2,
|
|
DHT_MSG_ERROR = 3
|
|
} dht_message_type;
|
|
|
|
typedef enum {
|
|
DHT_QUERY_NONE = 0,
|
|
DHT_QUERY_PING = 1,
|
|
DHT_QUERY_FIND_NODE = 2,
|
|
DHT_QUERY_GET_PEERS = 3,
|
|
DHT_QUERY_ANNOUNCE_PEER = 4
|
|
} dht_query_type;
|
|
|
|
typedef enum {
|
|
DHT_ERR_GENERIC = 201,
|
|
DHT_ERR_SERVER = 202,
|
|
DHT_ERR_PROTOCOL = 203,
|
|
DHT_ERR_METHOD_UNKNOWN = 204
|
|
} dht_error_code;
|
|
|
|
typedef struct {
|
|
uint8_t id[20];
|
|
uint8_t family; /* TRACKER_ADDR_IPV4 or TRACKER_ADDR_IPV6 */
|
|
uint8_t addr[16];
|
|
uint16_t port; /* host byte order */
|
|
} dht_node;
|
|
|
|
typedef struct {
|
|
dht_message_type type;
|
|
dht_query_type query;
|
|
uint8_t transaction[DHT_MAX_TRANSACTION];
|
|
size_t transaction_len;
|
|
uint8_t id[20];
|
|
uint8_t target[20];
|
|
uint8_t info_hash[20];
|
|
uint16_t port;
|
|
uint8_t implied_port;
|
|
uint8_t want_ipv4;
|
|
uint8_t want_ipv6;
|
|
uint8_t token[DHT_MAX_TOKEN];
|
|
size_t token_len;
|
|
dht_node nodes[DHT_MAX_NODES];
|
|
size_t node_count;
|
|
tracker_peer peers[TRACKER_MAX_PEERS];
|
|
size_t peer_count;
|
|
int error_code;
|
|
char error_message[DHT_MAX_ERROR];
|
|
} dht_message;
|
|
|
|
int dht_write_ping_query(const uint8_t *tx, size_t tx_len,
|
|
const uint8_t id[20],
|
|
uint8_t *buf, size_t cap, size_t *written);
|
|
int dht_write_find_node_query(const uint8_t *tx, size_t tx_len,
|
|
const uint8_t id[20],
|
|
const uint8_t target[20],
|
|
uint8_t want_ipv4, uint8_t want_ipv6,
|
|
uint8_t *buf, size_t cap, size_t *written);
|
|
int dht_write_get_peers_query(const uint8_t *tx, size_t tx_len,
|
|
const uint8_t id[20],
|
|
const uint8_t info_hash[20],
|
|
uint8_t want_ipv4, uint8_t want_ipv6,
|
|
uint8_t *buf, size_t cap, size_t *written);
|
|
int dht_write_announce_peer_query(const uint8_t *tx, size_t tx_len,
|
|
const uint8_t id[20],
|
|
const uint8_t info_hash[20],
|
|
uint16_t port,
|
|
const uint8_t *token, size_t token_len,
|
|
uint8_t implied_port,
|
|
uint8_t *buf, size_t cap, size_t *written);
|
|
|
|
int dht_write_ping_response(const uint8_t *tx, size_t tx_len,
|
|
const uint8_t id[20],
|
|
uint8_t *buf, size_t cap, size_t *written);
|
|
int dht_write_nodes_response(const uint8_t *tx, size_t tx_len,
|
|
const uint8_t id[20],
|
|
const uint8_t *token, size_t token_len,
|
|
const dht_node *nodes, size_t node_count,
|
|
uint8_t *buf, size_t cap, size_t *written);
|
|
int dht_write_peers_response(const uint8_t *tx, size_t tx_len,
|
|
const uint8_t id[20],
|
|
const uint8_t *token, size_t token_len,
|
|
const tracker_peer *peers, size_t peer_count,
|
|
uint8_t *buf, size_t cap, size_t *written);
|
|
int dht_write_error(const uint8_t *tx, size_t tx_len, int code,
|
|
const char *message,
|
|
uint8_t *buf, size_t cap, size_t *written);
|
|
|
|
int dht_parse_message(const uint8_t *packet, size_t len, dht_message *out);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* TORRENT_TRACKER_H */
|