Beispiel #1
0
 /**
  * Initializes the object with the config class.
  *
  * File logging can use 'file_path_messages' and file_path_errors params,
  * or logs to self::DEFAULT_LOG_PATH by default (both errors and messages).
  *
  * @param PHPTracker_Config_Interface $config
  */
 public function __construct(PHPTracker_Config_Interface $config = null)
 {
     if (!isset($config)) {
         // If no config set, we create empty config to get the default values.
         $config = new PHPTracker_Config_Simple(array());
     }
     $this->config = $config;
     $this->file_path_messages = $this->config->get('file_path_messages', false, self::DEFAULT_LOG_PATH);
     $this->file_path_errors = $this->config->get('file_path_errors', false, $this->file_path_messages);
 }
Beispiel #2
0
 /**
  * Manages handshaking with the client.
  *
  * If seeders_stop_seeding config key is set to a number greater than 0,
  * we check if we have at least N seeders beyond ourselves for the requested
  * torrent and if so, stop seeding (to spare bandwith).
  *
  * @throws PHPTracker_Seeder_Error_CloseConnection In case when the reqeust is invalid or we don't want or cannot serve the requested torrent.
  * @param PHPTracker_Seeder_Client $client
  */
 protected function shakeHand(PHPTracker_Seeder_Client $client)
 {
     $protocol_length = unpack('C', $client->socketRead(1));
     $protocol_length = current($protocol_length);
     if (($protocol = $client->socketRead($protocol_length)) !== self::PROTOCOL_STRING) {
         $this->logger->logError("Client tries to connect with unsupported protocol: " . substr($protocol, 0, 100) . ". Closing connection.");
         throw new PHPTracker_Seeder_Error_CloseConnection('Unsupported protocol.');
     }
     // 8 reserved void bytes.
     $client->socketRead(8);
     $info_hash = $client->socketRead(20);
     $client->peer_id = $client->socketRead(20);
     $info_hash_readable = unpack('H*', $info_hash);
     $info_hash_readable = reset($info_hash_readable);
     $torrent = $this->persistence->getTorrent($info_hash);
     if (!isset($torrent)) {
         throw new PHPTracker_Seeder_Error_CloseConnection('Unknown info hash.');
     }
     $client->torrent = $torrent;
     // If we have X other seeders already, we stop seeding on our own.
     if (0 < ($seeders_stop_seeding = $this->config->get('seeders_stop_seeding', false, 0))) {
         $stats = $this->persistence->getPeerStats($info_hash, $this->peer_id);
         if ($stats['complete'] >= $seeders_stop_seeding) {
             $this->logger->logMessage("External seeder limit ({$seeders_stop_seeding}) reached for info hash {$info_hash_readable}, stopping seeding.");
             throw new PHPTracker_Seeder_Error_CloseConnection('Stop seeding, we have others to seed.');
         }
     }
     // Our handshake signal.
     $client->socketWrite(pack('C', strlen(self::PROTOCOL_STRING)) . self::PROTOCOL_STRING . pack('a8', '') . $info_hash . pack('a20', $this->peer_id));
     $this->logger->logMessage("Handshake completed with peer {$client->peer_id} with address {$client->address}:{$client->port}, info hash: {$info_hash_readable}.");
 }
Beispiel #3
0
 /**
  * Announce a peer to be tracked and return message to the client.
  *
  * This methods needs 'interval' key to be set in the config of the class
  * (not i the GET!). This is a number representing seconds for the client to
  * wait for the next announcement.
  *
  * Optional config key 'load_balancing' (ON by defailt) adds 10% dispersion
  * to the interval value to avoid possible announce peeks.
  *
  * @param PHPTracker_Config_Interface $get Config-like representation of the CGI parameters (aka. GET) sent.
  * @return string
  */
 public function announce(PHPTracker_Config_Interface $get)
 {
     try {
         try {
             list($info_hash, $peer_id, $port, $uploaded, $downloaded, $left) = $get->getMulti(array('info_hash', 'peer_id', 'port', 'uploaded', 'downloaded', 'left'), true);
         } catch (PHPTracker_Config_Error_Missing $e) {
             return $this->announceFailure("Invalid get parameters; " . $e->getMessage());
         }
         // IP address might be set explicitly in the GET.
         $ip = $get->get('ip', false, $this->config->get('ip'));
         $event = $get->get('event', false, '');
         if (20 != strlen($info_hash)) {
             return $this->announceFailure("Invalid length of info_hash.");
         }
         if (20 != strlen($peer_id)) {
             return $this->announceFailure("Invalid length of info_hash.");
         }
         if (!(is_numeric($port) && is_int($port = $port + 0) && 0 <= $port)) {
             return $this->announceFailure("Invalid port value.");
         }
         if (!(is_numeric($uploaded) && is_int($uploaded = $uploaded + 0) && 0 <= $uploaded)) {
             return $this->announceFailure("Invalid uploaded value.");
         }
         if (!(is_numeric($downloaded) && is_int($downloaded = $downloaded + 0) && 0 <= $downloaded)) {
             return $this->announceFailure("Invalid downloaded value.");
         }
         if (!(is_numeric($left) && is_int($left = $left + 0) && 0 <= $left)) {
             return $this->announceFailure("Invalid left value.");
         }
         $interval = intval($this->config->get('interval'));
         $this->persistence->saveAnnounce($info_hash, $peer_id, $ip, $port, $downloaded, $uploaded, $left, 'completed' == $event ? 'complete' : null, 'stopped' == $event ? 0 : $interval * 2);
         $peers = $this->persistence->getPeers($info_hash, $peer_id, $get->get('compact', false, false), $get->get('no_peer_id', false, false));
         $peer_stats = $this->persistence->getPeerStats($info_hash, $peer_id);
         if (true === $this->config->get('load_balancing', false, true)) {
             // Load balancing for tracker announcements.
             $interval = $interval + mt_rand(round($interval / -10), round($interval / 10));
         }
         $announce_response = array('interval' => $interval, 'complete' => intval($peer_stats['complete']), 'incomplete' => intval($peer_stats['incomplete']), 'peers' => $peers);
         return PHPTracker_Bencode_Builder::build($announce_response);
     } catch (Exception $e) {
         trigger_error('Failure while announcing: ' . $e->getMessage(), E_USER_WARNING);
         return $this->announceFailure("Failed to announce because of internal server error.");
     }
 }
Beispiel #4
0
 /**
  * If there is no DB connection established yet, it connects and populates self::$connection attribute.
  *
  * Also "wakes up" connection if it has gone away.
  */
 protected function lazyConnect()
 {
     if (isset($this->connection)) {
         // Connection might have gone away.
         $this->connection->ping();
         return;
     }
     list($host, $user, $password, $database) = $this->config->getMulti(array('db_host', 'db_user', 'db_password', 'db_name'));
     $this->connection = new mysqli($host, $user, $password);
     if (mysqli_connect_errno()) {
         throw new PHPTracker_Persistence_Error('Unable to connect to mysql database: ' . mysqli_connect_error());
     }
     if (false === $this->connection->select_db($database)) {
         throw new PHPTracker_Persistence_Error("Unable to select database: {$database}.\n" . $this->connection->error);
     }
 }
Beispiel #5
0
 /**
  * Save announce for all the torrents in the database so clients know where to connect.
  *
  * This method runs in infinite loop repeating announcing every self::ANNOUNCE_INTERVAL seconds.
  */
 protected function announce()
 {
     $persistence = $this->config->get('persistence');
     $iterations = 0;
     do {
         $all_torrents = $persistence->getAllInfoHash();
         foreach ($all_torrents as $torrent_info) {
             $persistence->saveAnnounce($torrent_info['info_hash'], $this->peer->peer_id, $this->peer->external_address, $this->peer->port, $torrent_info['length'], 0, 0, 'complete', self::ANNOUNCE_INTERVAL);
         }
         $this->logger->logMessage('Seeder server announced itself for ' . count($all_torrents) . " torrents at address {$this->peer->external_address}:{$this->peer->port} (announces every " . self::ANNOUNCE_INTERVAL . 's).');
         sleep(self::ANNOUNCE_INTERVAL);
     } while (++$iterations < self::STOP_AFTER_ITERATIONS);
     // Memory leak prevention, see self::STOP_AFTER_ITERATIONS.
     $this->logger->logMessage('Announce process restarts to prevent memory leaks.');
     exit(0);
 }