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