1 /*
2 * exttrack.cpp
3 *
4 * Implements an external tracker client. Currently uses BT tracker protocol.
5 * If SwarmIDs are not SHA1 hashes they are hashed with a SHA1 MDC to turn
6 * them into an infohash. Only HTTP trackers supported at the moment.
7 *
8 * TODO:
9 * - SwarmManager reregistrations
10 * - IETF PPSP tracker protocol
11 *
12 * Created by Arno Bakker
13 * Copyright 2013-2016 Vrije Universiteit Amsterdam. All rights reserved.
14 */
15 #include "swift.h"
16 #include <event2/http.h>
17 #include <event2/http_struct.h>
18 #include <sstream>
19
20 #define exttrack_debug true
21
22
23 using namespace swift;
24
25
26 // https://wiki.theory.org/BitTorrentSpecification#peer_id
27 #define BT_PEER_ID_LENGTH 20 // bytes
28 #define BT_PEER_ID_PREFIX "-SW1000-"
29
30 #define BT_BENCODE_STRING_SEP ":"
31 #define BT_BENCODE_INT_SEP "e"
32
33 #define BT_FAILURE_REASON_DICT_KEY "failure reason"
34 #define BT_PEERS_IPv4_DICT_KEY "5:peers" // 5: to avoid confusion with a 'peers' list with one 6-byte entry
35 #define BT_INTERVAL_DICT_KEY "interval"
36 #define BT_PEERS_IPv6_DICT_KEY "6:peers6"
37
38 #define BT_INITIAL_REPORT_INTERVAL 30 // seconds
39
40
41 typedef enum {
42 BENCODED_INT,
43 BENCODED_STRING
44 } bencoded_type_t;
45
46
47 static int ParseBencodedPeers(struct evbuffer *evb, std::string key, peeraddrs_t *peerlist);
48 static int ParseBencodedValue(struct evbuffer *evb, struct evbuffer_ptr &startevbp, std::string key,
49 bencoded_type_t valuetype, char **valueptr);
50
ExternalTrackerClient(std::string url)51 ExternalTrackerClient::ExternalTrackerClient(std::string url) : url_(url), report_last_time_(0),
52 report_interval_(BT_INITIAL_REPORT_INTERVAL), reported_complete_(false)
53 {
54 // Create PeerID
55 peerid_ = new uint8_t[BT_PEER_ID_LENGTH];
56
57 int ret = 0;
58 #ifdef OPENSSL
59 strcpy((char *)peerid_,BT_PEER_ID_PREFIX);
60 ret = RAND_bytes(&peerid_[strlen(BT_PEER_ID_PREFIX)], BT_PEER_ID_LENGTH-strlen(BT_PEER_ID_PREFIX));
61 #endif
62 if (ret != 1) {
63 // Fallback and no OPENSSL option
64 char buf[32];
65 std::ostringstream oss;
66 oss << BT_PEER_ID_PREFIX;
67 sprintf(buf,"%05d", rand());
68 oss << buf;
69 sprintf(buf,"%05d", rand());
70 oss << buf;
71 sprintf(buf,"%05d", rand());
72 oss << buf;
73 std::string randstr = oss.str().substr(0,BT_PEER_ID_LENGTH);
74 strcpy((char *)peerid_,randstr.c_str());
75 }
76 }
77
~ExternalTrackerClient()78 ExternalTrackerClient::~ExternalTrackerClient()
79 {
80 if (peerid_ != NULL)
81 delete peerid_;
82 }
83
84
Contact(ContentTransfer * transfer,std::string event,exttrack_peerlist_callback_t callback)85 int ExternalTrackerClient::Contact(ContentTransfer *transfer, std::string event, exttrack_peerlist_callback_t callback)
86 {
87 Address myaddr = Channel::BoundAddress(Channel::default_socket());
88 std::string q = CreateQuery(transfer,myaddr,event);
89 if (q.length() == 0)
90 return -1;
91 else {
92 if (event == EXTTRACK_EVENT_COMPLETED)
93 reported_complete_ = true;
94
95 report_last_time_ = NOW;
96
97 ExtTrackCallbackRecord *callbackrec = new ExtTrackCallbackRecord(transfer->td(),callback);
98 return HTTPConnect(q,callbackrec);
99 }
100 }
101
102 /** IP in myaddr currently unused */
CreateQuery(ContentTransfer * transfer,Address myaddr,std::string event)103 std::string ExternalTrackerClient::CreateQuery(ContentTransfer *transfer, Address myaddr, std::string event)
104 {
105 Sha1Hash infohash;
106
107 // Should be per swarm, now using global upload, just to monitor sharing activity
108 uint64_t uploaded = Channel::global_bytes_up;
109 uint64_t downloaded = swift::SeqComplete(transfer->td());
110 uint64_t left = 0;
111 if (transfer->ttype() == FILE_TRANSFER) {
112 infohash = transfer->swarm_id().roothash();
113 if (downloaded > swift::Size(transfer->td()))
114 left = 0;
115 else
116 left = swift::Size(transfer->td()) - downloaded;
117
118 // "No completed is sent if the file was complete when started. "
119 // http://www.bittorrent.org/beps/bep_0003.html
120 if (event == EXTTRACK_EVENT_STARTED && left == 0)
121 reported_complete_ = true;
122 } else {
123 SwarmPubKey spubkey = transfer->swarm_id().spubkey();
124 infohash = Sha1Hash(spubkey.bits(),spubkey.length());
125 left = 0x7fffffffffffffffULL;
126 }
127
128 // See
129 // http://www.bittorrent.org/beps/bep_0003.html
130 // https://wiki.theory.org/BitTorrent_Tracker_Protocol
131
132 char *esc = NULL;
133 std::ostringstream oss;
134
135 oss << "info_hash=";
136 esc = evhttp_uriencode((const char *)infohash.bytes(),Sha1Hash::SIZE,false);
137 if (esc == NULL)
138 return "";
139 oss << esc;
140 free(esc);
141 oss << "&";
142
143 oss << "peer_id=";
144 esc = evhttp_uriencode((const char *)peerid_,BT_PEER_ID_LENGTH,false);
145 if (esc == NULL)
146 return "";
147 oss << esc;
148 free(esc);
149 oss << "&";
150
151 // ip= currently unused
152
153 oss << "port=";
154 oss << myaddr.port();
155 oss << "&";
156
157 oss << "uploaded=";
158 oss << uploaded;
159 oss << "&";
160
161 oss << "downloaded=";
162 oss << downloaded;
163 oss << "&";
164
165 oss << "left=";
166 oss << left;
167 oss << "&";
168
169 // Request compacted peerlist, is most common http://www.bittorrent.org/beps/bep_0023.html
170 oss << "compact=1";
171
172 if (event.length() > 0) {
173 oss << "&";
174 oss << "event=";
175 oss << event;
176 }
177
178 return oss.str();
179 }
180
181
ExternalTrackerClientHTTPResponseCallback(struct evhttp_request * req,void * callbackrecvoid)182 static void ExternalTrackerClientHTTPResponseCallback(struct evhttp_request *req, void *callbackrecvoid)
183 {
184 if (exttrack_debug)
185 fprintf(stderr,"exttrack: Callback: ENTER %p\n", callbackrecvoid);
186
187 ExtTrackCallbackRecord *callbackrec = (ExtTrackCallbackRecord *)callbackrecvoid;
188 if (callbackrec == NULL)
189 return;
190 if (callbackrec->callback_ == NULL)
191 return;
192
193
194 if (req->response_code != HTTP_OK) {
195 callbackrec->callback_(callbackrec->td_,"Unexpected HTTP Response Code",0,peeraddrs_t());
196 delete callbackrec;
197 return;
198 }
199
200 // Create a copy of the response, as we do destructive parsing
201 struct evbuffer *evb = evhttp_request_get_input_buffer(req);
202 size_t copybuflen = evbuffer_get_length(evb);
203 char *copybuf = new char[copybuflen];
204 int ret = evbuffer_remove(evb,copybuf,copybuflen);
205 if (ret < 0) {
206 delete copybuf;
207
208 callbackrec->callback_(callbackrec->td_,"Could not read HTTP body",0,peeraddrs_t());
209 delete callbackrec;
210 return;
211 }
212
213 //fprintf(stderr,"exttrack: Raw response <%s>\n", copybuf );
214
215
216 evb = evbuffer_new();
217 evbuffer_add(evb,copybuf,copybuflen);
218
219 // Find "failure reason" string following BT spec
220 struct evbuffer_ptr startevbp = evbuffer_search(evb,BT_FAILURE_REASON_DICT_KEY,strlen(BT_FAILURE_REASON_DICT_KEY),NULL);
221 if (startevbp.pos != -1) {
222 char *valuebytes = NULL;
223 std::string errorstr;
224 int ret = ParseBencodedValue(evb,startevbp,BT_FAILURE_REASON_DICT_KEY,BENCODED_STRING,&valuebytes);
225 if (ret < 0) {
226 errorstr = "Error parsing tracker response: failure reason";
227 } else {
228 errorstr = "Tracker responded: "+std::string(valuebytes);
229 }
230 callbackrec->callback_(callbackrec->td_,errorstr,0,peeraddrs_t());
231 delete callbackrec;
232
233 evbuffer_free(evb);
234 delete copybuf;
235 free(valuebytes);
236 return; // failure case done
237 }
238 evbuffer_free(evb);
239
240 // If not failure, find tracker report interval
241 uint32_t interval=0;
242
243 evb = evbuffer_new();
244 evbuffer_add(evb,copybuf,copybuflen);
245 startevbp = evbuffer_search(evb,BT_INTERVAL_DICT_KEY,strlen(BT_INTERVAL_DICT_KEY),NULL);
246 if (startevbp.pos != -1) {
247 char *valuebytes = NULL;
248 std::string errorstr="";
249 int ret = ParseBencodedValue(evb,startevbp,BT_INTERVAL_DICT_KEY,BENCODED_INT,&valuebytes);
250 if (ret < 0) {
251 delete copybuf;
252 evbuffer_free(evb);
253
254 callbackrec->callback_(callbackrec->td_,"Error parsing tracker response: interval",0,peeraddrs_t());
255 delete callbackrec;
256 return;
257 } else {
258 ret = sscanf(valuebytes,"%" PRIu32 "",&interval);
259 if (ret != 1) {
260 free(valuebytes);
261 delete copybuf;
262 evbuffer_free(evb);
263
264 callbackrec->callback_(callbackrec->td_,"Error parsing tracker response: interval",0,peeraddrs_t());
265 delete callbackrec;
266 return;
267 }
268
269 free(valuebytes);
270 if (exttrack_debug)
271 fprintf(stderr,"exttrack: Got interval %" PRIu32 "\n", interval);
272 }
273 }
274 evbuffer_free(evb);
275
276
277 // If not failure, find peers key whose value is compact IPv4 addresses
278 // http://www.bittorrent.org/beps/bep_0023.html
279 peeraddrs_t peerlist;
280
281 evb = evbuffer_new();
282 evbuffer_add(evb,copybuf,copybuflen);
283 ret = ParseBencodedPeers(evb, BT_PEERS_IPv4_DICT_KEY,&peerlist);
284 if (ret < 0) {
285 delete copybuf;
286 evbuffer_free(evb);
287
288 callbackrec->callback_(callbackrec->td_,"Error parsing tracker response: peerlist",interval,peeraddrs_t());
289 delete callbackrec;
290 return;
291 }
292 evbuffer_free(evb);
293
294 if (exttrack_debug)
295 fprintf(stderr,"btrack: Got " PRISIZET " IPv4 peers\n", peerlist.size());
296
297 // If not failure, find peers key whose value is compact IPv6 addresses
298 // http://www.bittorrent.org/beps/bep_0007.html
299 evb = evbuffer_new();
300 evbuffer_add(evb,copybuf,copybuflen);
301 ret = ParseBencodedPeers(evb, BT_PEERS_IPv6_DICT_KEY,&peerlist);
302 if (ret < 0) {
303 delete copybuf;
304 evbuffer_free(evb);
305
306 callbackrec->callback_(callbackrec->td_,"Error parsing tracker response: peerlist",interval,peeraddrs_t());
307 delete callbackrec;
308 return;
309 }
310 evbuffer_free(evb);
311
312 if (exttrack_debug)
313 fprintf(stderr,"btrack: Got " PRISIZET " peers total\n", peerlist.size());
314
315 // Report success
316 callbackrec->callback_(callbackrec->td_,"",interval,peerlist);
317
318 delete copybuf;
319 delete callbackrec;
320 }
321
322
HTTPConnect(std::string query,ExtTrackCallbackRecord * callbackrec)323 int ExternalTrackerClient::HTTPConnect(std::string query,ExtTrackCallbackRecord *callbackrec)
324 {
325 std::string fullurl = url_+"?"+query;
326
327 if (exttrack_debug)
328 fprintf(stderr,"exttrack: HTTPConnect: %s\n", fullurl.c_str());
329
330 struct evhttp_uri *evu = evhttp_uri_parse(fullurl.c_str());
331 if (evu == NULL)
332 return -1;
333
334 int fullpathlen = strlen(evhttp_uri_get_path(evu))+strlen("?")+strlen(evhttp_uri_get_query(evu));
335 char *fullpath = new char[fullpathlen+1];
336 strcpy(fullpath,evhttp_uri_get_path(evu));
337 strcat(fullpath,"?");
338 strcat(fullpath,evhttp_uri_get_query(evu));
339
340 //fprintf(stderr,"exttrack: HTTPConnect: Composed fullpath %s\n", fullpath );
341
342 // Create HTTP client
343 struct evhttp_connection *cn = evhttp_connection_base_new(Channel::evbase, NULL, evhttp_uri_get_host(evu),
344 evhttp_uri_get_port(evu));
345 struct evhttp_request *req = evhttp_request_new(ExternalTrackerClientHTTPResponseCallback, (void *)callbackrec);
346
347 // Make request to server
348 evhttp_make_request(cn, req, EVHTTP_REQ_GET, fullpath);
349 evhttp_add_header(req->output_headers, "Host", evhttp_uri_get_host(evu));
350
351 delete fullpath;
352 evhttp_uri_free(evu);
353
354 return 0;
355 }
356
357
ParseBencodedPeers(struct evbuffer * evb,std::string key,peeraddrs_t * peerlist)358 static int ParseBencodedPeers(struct evbuffer *evb, std::string key, peeraddrs_t *peerlist)
359 {
360 struct evbuffer_ptr startevbp = evbuffer_search(evb,key.c_str(),key.length(),NULL);
361 if (startevbp.pos != -1) {
362 char *valuebytes = NULL;
363 std::string errorstr;
364 int ret = ParseBencodedValue(evb,startevbp,key,BENCODED_STRING,&valuebytes);
365 if (ret < 0)
366 return -1;
367
368 int peerlistenclen = ret;
369 //fprintf(stderr,"exttrack: Peerlist encoded len %d\n", peerlistenclen );
370
371 // Decompact addresses
372 struct evbuffer *evb2 = evbuffer_new();
373 evbuffer_add(evb2,valuebytes,peerlistenclen);
374
375
376 int family=AF_INET;
377 int enclen=6;
378 if (key == BT_PEERS_IPv6_DICT_KEY) {
379 family = AF_INET6;
380 enclen = 18;
381 }
382 for (int i=0; i<peerlistenclen/enclen; i++) {
383 // Careful: if PPSPP on the wire encoding changes, we can't use
384 // this one anymore.
385 Address addr = evbuffer_remove_pexaddr(evb2,family);
386 peerlist->push_back(addr);
387
388 if (exttrack_debug)
389 fprintf(stderr,"exttrack: Peerlist parsed %d %s\n", i, addr.str().c_str());
390 }
391 evbuffer_free(evb2);
392
393 return 0;
394 } else
395 return 0; // Could be attempt to look for IPv6 peers in IPv4 only dict.
396 }
397
398
399 /** Failure reported, extract string from bencoded dictionary */
ParseBencodedValue(struct evbuffer * evb,struct evbuffer_ptr & startevbp,std::string key,bencoded_type_t valuetype,char ** valueptr)400 static int ParseBencodedValue(struct evbuffer *evb, struct evbuffer_ptr &startevbp, std::string key,
401 bencoded_type_t valuetype, char **valueptr)
402 {
403 //fprintf(stderr,"exttrack: Callback: key %s starts at " PRISIZET "\n", key.c_str(), startevbp.pos );
404
405 size_t pastkeypos = startevbp.pos+key.length();
406 if (valuetype == BENCODED_INT)
407 pastkeypos++; // skip 'i'
408
409 //fprintf(stderr,"exttrack: Callback: key ends at " PRISIZET "\n", pastkeypos );
410
411 int ret = evbuffer_ptr_set(evb, &startevbp, pastkeypos, EVBUFFER_PTR_SET);
412 if (ret < 0)
413 return -1;
414
415 std::string separator = BT_BENCODE_STRING_SEP;
416 if (valuetype == BENCODED_INT)
417 separator = BT_BENCODE_INT_SEP;
418
419 // Find separator to determine string len
420 struct evbuffer_ptr endevbp = evbuffer_search(evb,separator.c_str(),strlen(separator.c_str()),&startevbp);
421 if (endevbp.pos == -1)
422 return -1;
423
424 //fprintf(stderr,"exttrack: Callback: separator at " PRISIZET " key len %d\n", endevbp.pos, key.length() );
425
426 size_t intcstrlen = endevbp.pos - startevbp.pos;
427
428 //fprintf(stderr,"exttrack: Callback: value length " PRISIZET "\n", intcstrlen );
429
430 size_t strpos = endevbp.pos+1;
431
432 //fprintf(stderr,"exttrack: Callback: value starts at " PRISIZET "\n", strpos );
433
434 ret = evbuffer_ptr_set(evb, &startevbp, strpos, EVBUFFER_PTR_SET);
435 if (ret < 0)
436 return -1;
437
438 // Remove all before
439 ret = evbuffer_drain(evb,strpos-1-intcstrlen);
440 if (ret < 0)
441 return -1;
442
443 char *intcstr = new char[intcstrlen+1];
444 intcstr[intcstrlen] = '\0';
445 ret = evbuffer_remove(evb,intcstr,intcstrlen);
446 if (ret < 0) {
447 delete intcstr;
448 return -1;
449 }
450
451
452 //fprintf(stderr,"exttrack: Callback: Length value string %s\n", intcstr );
453
454 if (valuetype == BENCODED_INT) {
455 *valueptr = intcstr;
456 return intcstrlen;
457 }
458 // For strings carry on
459
460 int slen=0;
461 ret = sscanf(intcstr,"%d", &slen);
462 delete intcstr;
463 if (ret != 1)
464 return -1;
465
466 //fprintf(stderr,"exttrack: Callback: Length value int %d\n", slen );
467
468 // Drain colon
469 ret = evbuffer_drain(evb,1);
470 if (ret < 0)
471 return -1;
472
473 // NOTE: actual bytes may also contain '\0', C string is for convenience when it isn't pure binary.
474 char *valuecstr = new char[slen+1];
475 valuecstr[slen] = '\0';
476 ret = evbuffer_remove(evb,valuecstr,slen);
477 if (ret < 0) {
478 delete valuecstr;
479 return -1;
480 } else {
481 //fprintf(stderr,"exttrack: Callback: Value string <%s>\n", valuecstr );
482
483 *valueptr = valuecstr;
484 // do not delete valuecstr;
485 return slen;
486 }
487 }
488
489
490