/* * lftp - file transfer program * * Copyright (c) 1996-2015 by Alexander V. Lukyanov (lav@yars.free.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "Torrent.h" #include "TorrentTracker.h" #include "log.h" #include "url.h" #include "misc.h" #include "plural.h" void TorrentTracker::AddURL(const char *url) { LogNote(4,"Tracker URL is `%s'",url); ParsedURL u(url,true); if(u.proto.ne("http") && u.proto.ne("https") && u.proto.ne("udp")) { LogError(1,"unsupported tracker protocol `%s', must be http, https or udp",u.proto.get()); return; } xstring& tracker_url=*new xstring(url); if(u.proto.ne("udp")) { if(!u.path || !u.path[0]) tracker_url.append('/'); // fix the URL: append either ? or & if missing. if(tracker_url.last_char()!='?' && tracker_url.last_char()!='&') tracker_url.append(tracker_url.instr('?')>=0?'&':'?'); } tracker_urls.append(&tracker_url); } TorrentTracker::TorrentTracker(Torrent *p,const char *url) : parent(p), current_tracker(0), tracker_timer(600), tracker_timeout_timer(120), started(false), tracker_no(0) { AddURL(url); } bool TorrentTracker::IsActive() const { return backend && backend->IsActive(); } void TorrentTracker::Shutdown() { if(Failed()) // don't stop a failed tracker return; // stop if have started or at least processing a start request if(started || IsActive()) SendTrackerRequest("stopped"); } void TorrentTracker::SetError(const char *e) { if(tracker_urls.count()<=1) error=new Error(-1,e,true); else { LogError(3,"Tracker error: %s, using next tracker URL",e); tracker_urls.remove(current_tracker--); NextTracker(); // retry immediately tracker_timer.Stop(); } } bool TorrentTracker::AddPeerCompact(const char *compact_addr,int len) const { sockaddr_u a; if(!a.set_compact(compact_addr,len)) return false; Enter(parent); parent->AddPeer(new TorrentPeer(parent,&a,tracker_no)); Leave(parent); return true; } bool TorrentTracker::AddPeer(const xstring& addr,int port) const { sockaddr_u a; #if INET6 if(addr.instr(':')>=0) { a.sa.sa_family=AF_INET6; if(inet_pton(AF_INET6,addr,&a.in6.sin6_addr)<=0) return false; } else #endif { a.sa.sa_family=AF_INET; if(!inet_aton(addr,&a.in.sin_addr)) return false; } a.set_port(port); Enter(parent); parent->AddPeer(new TorrentPeer(parent,&a,tracker_no)); Leave(parent); return true; } int TorrentTracker::Do() { int m=STALL; if(Failed()) return m; if(backend && backend->IsActive()) { if(tracker_timeout_timer.Stopped()) { LogError(3,"Tracker timeout"); NextTracker(); return MOVED; } } else { if(tracker_timer.Stopped()) { parent->CleanPeers(); SendTrackerRequest(0); } } return m; } void TorrentTracker::CreateTrackerBackend() { backend=0; ParsedURL u(GetURL(),true); if(u.proto.eq("udp")) { backend=new UdpTracker(this,&u); } else if(u.proto.eq("http") || u.proto.eq("https")) { backend=new HttpTracker(this,&u); } } void TorrentTracker::NextTracker() { current_tracker++; if(current_tracker>=tracker_urls.count()) current_tracker=0; tracker_timer.Reset(); CreateTrackerBackend(); } void TorrentTracker::Start() { if(backend || Failed()) return; CreateTrackerBackend(); SendTrackerRequest("started"); } void TorrentTracker::SendTrackerRequest(const char *event) { backend->SendTrackerRequest(event); tracker_timeout_timer.Reset(); } const char *TorrentTracker::Status() const { if(error) return error->Text(); if(!backend) return _("not started"); if(backend->IsActive()) return backend->Status(); return xstring::format(_("next request in %s"),NextRequestIn()); } // TrackerBackend const xstring& TrackerBackend::GetInfoHash() const { return master->parent->GetInfoHash(); } const xstring& TrackerBackend::GetMyPeerId() const { return master->parent->GetMyPeerId(); } int TrackerBackend::GetPort() const { return master->parent->GetPort(); } unsigned long long TrackerBackend::GetTotalSent() const { return master->parent->GetTotalSent(); } unsigned long long TrackerBackend::GetTotalRecv() const { return master->parent->GetTotalRecv(); } unsigned long long TrackerBackend::GetTotalLeft() const { return master->parent->GetTotalLeft(); } bool TrackerBackend::HasMetadata() const { return master->parent->HasMetadata(); } bool TrackerBackend::Complete() const { return master->parent->Complete(); } int TrackerBackend::GetWantedPeersCount() const { return master->parent->GetWantedPeersCount(); } const xstring& TrackerBackend::GetMyKey() const { return master->parent->GetMyKey(); } unsigned TrackerBackend::GetMyKeyNum() const { return master->parent->GetMyKeyNum(); } const char *TrackerBackend::GetTrackerId() const { return master->tracker_id; } bool TrackerBackend::ShuttingDown() const { return master->parent->ShuttingDown(); } void TrackerBackend::Started() const { master->started=true; } void TrackerBackend::TrackerRequestFinished() const { master->tracker_timer.Reset(); } // HttpTracker #define super TrackerBackend int HttpTracker::HandleTrackerReply() { if(tracker_reply->Error()) { SetError(tracker_reply->ErrorText()); t_session->Close(); tracker_reply=0; return MOVED; } if(!tracker_reply->Eof()) return STALL; t_session->Close(); int rest; Ref reply(BeNode::Parse(tracker_reply->Get(),tracker_reply->Size(),&rest)); if(!reply) { LogError(3,"Tracker reply parse error (data: %s)",tracker_reply->Dump()); tracker_reply=0; NextTracker(); return MOVED; } LogNote(10,"Received tracker reply:"); Log::global->Write(10,reply->Format()); if(ShuttingDown()) { tracker_reply=0; t_session=0; return MOVED; } Started(); if(reply->type!=BeNode::BE_DICT) { SetError("Reply: wrong reply type, must be DICT"); tracker_reply=0; return MOVED; } BeNode *b_failure_reason=reply->lookup("failure reason"); if(b_failure_reason) { if(b_failure_reason->type==BeNode::BE_STR) SetError(b_failure_reason->str); else SetError("Reply: wrong `failure reason' type, must be STR"); tracker_reply=0; return MOVED; } BeNode *b_interval=reply->lookup("interval",BeNode::BE_INT); if(b_interval) SetInterval(b_interval->num); SetTrackerID(reply->lookup_str("tracker id")); int peers_count=0; BeNode *b_peers=reply->lookup("peers"); if(b_peers) { if(b_peers->type==BeNode::BE_STR) { // binary model const char *data=b_peers->str; int len=b_peers->str.length(); LogNote(9,"peers have binary model, length=%d",len); while(len>=6) { if(AddPeerCompact(data,6)) peers_count++; data+=6; len-=6; } } else if(b_peers->type==BeNode::BE_LIST) { // dictionary model int count=b_peers->list.count(); LogNote(9,"peers have dictionary model, count=%d",count); for(int p=0; plist[p]; if(b_peer->type!=BeNode::BE_DICT) continue; BeNode *b_ip=b_peer->lookup("ip",BeNode::BE_STR); if(!b_ip) continue; BeNode *b_port=b_peer->lookup("port",BeNode::BE_INT); if(!b_port) continue; if(AddPeer(b_ip->str,b_port->num)) peers_count++; } } LogNote(4,plural("Received valid info about %d peer$|s$",peers_count),peers_count); } #if INET6 peers_count=0; b_peers=reply->lookup("peers6",BeNode::BE_STR); if(b_peers) { // binary model const char *data=b_peers->str; int len=b_peers->str.length(); while(len>=18) { if(AddPeerCompact(data,18)) peers_count++; data+=18; len-=18; } LogNote(4,plural("Received valid info about %d IPv6 peer$|s$",peers_count),peers_count); } #endif//INET6 tracker_reply=0; TrackerRequestFinished(); return MOVED; } int HttpTracker::Do() { int m=STALL; if(!IsActive()) return m; if(tracker_reply) m|=HandleTrackerReply(); return m; } void HttpTracker::SendTrackerRequest(const char *event) { if(!t_session) return; xstring request(GetURL()); request.appendf("info_hash=%s",url::encode(GetInfoHash(),URL_PATH_UNSAFE).get()); request.appendf("&peer_id=%s",url::encode(GetMyPeerId(),URL_PATH_UNSAFE).get()); request.appendf("&port=%d",GetPort()); request.appendf("&uploaded=%llu",GetTotalSent()); request.appendf("&downloaded=%llu",GetTotalRecv()); request.appendf("&left=%llu",HasMetadata()?GetTotalLeft():123456789ULL); request.append("&compact=1&no_peer_id=1"); if(event) request.appendf("&event=%s",event); const char *ip=ResMgr::Query("torrent:ip",0); if(ip && ip[0]) request.appendf("&ip=%s",ip); #if INET6 int port=Torrent::GetPortIPv4(); int port_ipv6=Torrent::GetPortIPv6(); const char *ipv6=ResMgr::Query("torrent:ipv6",0); if(port && ip && ip[0]) request.appendf("&ipv4=%s:%d",ip,port); if(port_ipv6) request.appendf("&ipv6=[%s]:%d",ipv6&&ipv6[0]?ipv6:Torrent::GetAddressIPv6(),port_ipv6); #endif int numwant=GetWantedPeersCount(); if(numwant>=0) request.appendf("&numwant=%d",numwant); const xstring& my_key=GetMyKey(); if(my_key) request.appendf("&key=%s",my_key.get()); const char *tracker_id=GetTrackerId(); if(tracker_id) request.appendf("&trackerid=%s",url::encode(tracker_id,URL_PATH_UNSAFE).get()); LogSend(4,request); t_session->Open(url::path_ptr(request),FA::RETRIEVE); t_session->SetFileURL(request); tracker_reply=new IOBufferFileAccess(t_session); } // UdpTracker int UdpTracker::Do() { int m=STALL; if(!peer) { // need to resolve addresses if(!resolver) { resolver=new Resolver(hostname,portname,"80"); resolver->Roll(); m=MOVED; } if(!resolver->Done()) return m; if(resolver->Error()) { SetError(resolver->ErrorMsg()); return(MOVED); } peer.set(resolver->Result()); peer_curr=0; resolver=0; try_number=0; m=MOVED; } if(!IsActive()) return m; if(sock==-1) { // need to create the socket sock=SocketCreate(peer[peer_curr].family(),SOCK_DGRAM,IPPROTO_UDP,hostname); if(sock==-1) { int saved_errno=errno; LogError(9,"socket: %s",strerror(saved_errno)); if(NonFatalError(saved_errno)) return m; xstring& str=xstring::format(_("cannot create socket of address family %d"), peer[peer_curr].family()); str.appendf(" (%s)",strerror(saved_errno)); SetError(str); return MOVED; } } if(current_action!=a_none) { if(!RecvReply()) { if(timeout_timer.Stopped()) { LogError(3,"request timeout"); NextPeer(); return MOVED; } return m; } return MOVED; } if(!has_connection_id) { // need to get connection id SendConnectRequest(); return MOVED; } SendEventRequest(); return MOVED; } void UdpTracker::NextPeer() { current_action=a_none; has_connection_id=false; connection_id=0; int old_peer=peer_curr; peer_curr++; if(peer_curr>=peer.count()) { peer_curr=0; try_number++; } // check if we need to create a socket of different address family if(old_peer!=peer_curr && peer[old_peer].family()!=peer[peer_curr].family()) { close(sock); sock=-1; } } bool UdpTracker::RecvReply() { if(!Ready(sock,POLLIN)) { Block(sock,POLLIN); return false; } Buffer buf; const int max_len=0x1000; sockaddr_u addr; socklen_t addr_len=sizeof(addr); int len=recvfrom(sock,buf.GetSpace(max_len),max_len,0,&addr.sa,&addr_len); if(len<0) { int saved_errno=errno; if(NonFatalError(saved_errno)) { Block(sock,POLLIN); return false; } SetError(xstring::format("recvfrom: %s",strerror(saved_errno))); return false; } if(len==0) { SetError("recvfrom: EOF?"); return false; } buf.SpaceAdd(len); LogRecv(10,xstring::format("got a packet from %s of length %d {%s}",addr.to_string(),len,buf.Dump())); if(len<16) { LogError(9,"ignoring too short packet"); return false; } unsigned tid=buf.UnpackUINT32BE(4); if(tid!=transaction_id) { LogError(9,"ignoring mismatching transaction packet (0x%08X!=0x%08X)",tid,transaction_id); return false; } int action=buf.UnpackUINT32BE(0); if(action!=current_action && action!=a_error) { LogError(9,"ignoring mismatching action packet (%d!=%d)",action,current_action); return false; } switch(action) { case a_none: abort(); case a_connect: connection_id=buf.UnpackUINT64BE(8); has_connection_id=true; LogNote(9,"connected"); break; case a_announce: case a_announce6: { SetInterval(buf.UnpackUINT32BE(8)); if(buf.Size()<20) break; unsigned leachers=buf.UnpackUINT32BE(12); unsigned seeders=buf.UnpackUINT32BE(16); LogNote(9,"leechers=%u seeders=%u",leachers,seeders); int peers_count=0; int compact_addr_size=6; if(current_action==a_announce6) compact_addr_size=18; for(int i=20; i+compact_addr_size<=buf.Size(); i+=compact_addr_size) { if(AddPeerCompact(buf.Get()+i,compact_addr_size)) peers_count++; } LogNote(4,plural("Received valid info about %d peer$|s$",peers_count),peers_count); current_event=ev_idle; TrackerRequestFinished(); break; } case a_scrape: // not implemented break; case a_error: SetError(buf.Get()+8); break; } current_action=a_none; try_number=0; return true; } bool UdpTracker::SendPacket(Buffer& req) { LogSend(10,xstring::format("sending a packet to %s of length %d {%s}",peer[peer_curr].to_string(),req.Size(),req.Dump())); int len=sendto(sock,req.Get(),req.Size(),0,&peer[peer_curr].sa,peer[peer_curr].addr_len()); if(len<0) { int saved_errno=errno; if(NonFatalError(saved_errno)) { Block(sock,POLLOUT); return false; } SetError(xstring::format("sendto: %s",strerror(saved_errno))); return false; } if(len=0 && e<=3) return map[e]; return "???"; } bool UdpTracker::SendEventRequest() { action_t action=a_announce; const char *a_name="announce"; #if INET6 if(peer[peer_curr].family()==AF_INET6) { action=a_announce6; a_name="announce6"; } #endif LogNote(9,"%s %s",a_name,EventToString(current_event)); assert(has_connection_id); assert(current_event!=ev_idle); Buffer req; req.PackUINT64BE(connection_id); req.PackUINT32BE(action); req.PackUINT32BE(NewTransactionId()); req.Append(GetInfoHash()); req.Append(GetMyPeerId()); req.PackUINT64BE(GetTotalRecv()); req.PackUINT64BE(GetTotalLeft()); req.PackUINT64BE(GetTotalSent()); req.PackUINT32BE(current_event); #if INET6 if(action==a_announce6) { const char *ip=ResMgr::Query("torrent:ipv6",0); char ip_packed[16]; memset(ip_packed,0,16); if(ip && ip[0]) inet_pton(AF_INET6,ip,ip_packed); req.Append(ip_packed,16); } else #endif { const char *ip=ResMgr::Query("torrent:ip",0); char ip_packed[4]; memset(ip_packed,0,4); if(ip && ip[0]) inet_pton(AF_INET,ip,ip_packed); req.Append(ip_packed,4); } req.PackUINT32BE(GetMyKeyNum()); req.PackUINT32BE(GetWantedPeersCount()); req.PackUINT16BE(GetPort()); if(!SendPacket(req)) return false; current_action=action; return true; } const char *UdpTracker::Status() const { if(resolver) return(_("Resolving host address...")); if(!has_connection_id) return(_("Connecting...")); if(current_action!=a_none) return _("Waiting for response..."); return ""; } void UdpTracker::SendTrackerRequest(const char *event) { current_event=ev_none; if(!event) return; if(!strcmp(event,"started")) current_event=ev_started; else if(!strcmp(event,"stopped")) current_event=ev_stopped; else if(!strcmp(event,"completed")) current_event=ev_completed; }