UDP Tracker announce in PHP - php

basically I am trying to get peers in php from a udp torrent tracker.
Purpose is analyzing traffic in different regions.
So I don't need to download the torrent just get the peer list and announce again to get a different set. I tried to use PHP
I have picked a open-source code that scrapes udp torrents and modified it.
here's the code
require_once(dirname(__FILE__) . '/tscraper.php');
class udptscraper extends tscraper{
/* $url: Tracker url like: udp://tracker.tld:port or udp://tracker.tld:port/announce
$infohash: Infohash string or array (max 74 items). 40 char long infohash.
*/
public function scrape($url,$infohash){
if(!is_array($infohash)){ $infohash = array($infohash); }
foreach($infohash as $hash){
if(!preg_match('#^[a-f0-9]{40}$#i',$hash)){ throw new ScraperException('Invalid infohash: ' . $hash . '.'); }
}
if(count($infohash) > 74){ throw new ScraperException('Too many infohashes provided.'); }
if(!preg_match('%udp://([^:/]*)(?::([0-9]*))?(?:/)?%si', $url, $m)){ throw new ScraperException('Invalid tracker url.'); }
$tracker = 'udp://' . $m[1];
$port = isset($m[2]) ? $m[2] : 80;
$transaction_id = mt_rand(0,65535);
$fp = fsockopen($tracker, $port, $errno, $errstr);
if(!$fp){ throw new ScraperException('Could not open UDP connection: ' . $errno . ' - ' . $errstr,0,true); }
stream_set_timeout($fp, $this->timeout);
$current_connid = "\x00\x00\x04\x17\x27\x10\x19\x80";
//Connection request
$packet = $current_connid . pack("N", 0) . pack("N", $transaction_id);
fwrite($fp,$packet);
//Connection response
$ret = fread($fp, 16);
if(strlen($ret) < 1){ throw new ScraperException('No connection response.',0,true); }
if(strlen($ret) < 16){ throw new ScraperException('Too short connection response.'); }
$retd = unpack("Naction/Ntransid",$ret);
if($retd['action'] != 0 || $retd['transid'] != $transaction_id){
throw new ScraperException('Invalid connection response.');
}
$current_connid = substr($ret,8,8);
//ANNOUNCE request
$hashes = '';
$pid ='O5214m2Y0z6178K1z090';
$key = mt_rand(0,65535);
$down =mt_rand(0,12345);
$left =mt_rand(0,12345);
$upped =mt_rand(0,12345);
$transaction_id = mt_rand(0,65535);
$event = 2;
$socket = socket_create_listen (19624);
foreach($infohash as $hash){ $hashes .= pack('H*', $hash); }
$packet = $current_connid . pack("N", 1) . pack("N", $transaction_id) . $hashes . pack("N", $pid) . pack("N", $down) . pack("N", $left) . pack("N", $upped) . pack("N", 0) . pack("N", 0) . pack("N", $key) . pack("N", -1) . pack("N", 19624);
fwrite($fp,$packet);
//ANNOUNCE response
$readlength = 20 + (6 * count($infohash));
$ret = fread($fp, $readlength);
echo $ret;
if(strlen($ret) < 1){ throw new ScraperException('No .',0,true); }
if(strlen($ret) < 8){ throw new ScraperException('Too short response.'); }
$retd = unpack("Naction/Ntransid",$ret);
// Todo check for error string if response = 3
if($retd['action'] != 1 || $retd['transid'] != $transaction_id){
throw new ScraperException('Invalid scrape response.');
}
if(strlen($ret) < $readlength){ throw new ScraperException('Too short scrape response.'); }
$torrents = array();
$index = 8;
foreach($infohash as $hash){
$retd = unpack("Ninterval/Nleechers/Nseeders/Nipaddr/NTCP",substr($ret,$index));
print_r($retd);
$retd['infohash'] = $hash;
$torrents[$hash] = $retd;
$index = $index + 12;
}
return($torrents);
}
}
try{
$timeout = 2;
$scraper = new udptscraper($timeout);
$ret = $scraper->scrape('udp://tracker.openbittorrent.com:80/announce',array('8B60D5838A2CE34294AF9E49FF990C5BEC6C61B1'));
//print_r($ret);
}catch(ScraperException $e){
echo('Error: ' . $e->getMessage() . "<br />\n");
echo('Connection error: ' . ($e->isConnectionError() ? 'yes' : 'no') . "<br />\n");
}
?>
I am using only 1 HASH value
Some help please.

EDIT : The tracker server sometimes send the response message with peer list of peers replaced by a 6 bytes per peer. The first 4 bytes are the host IP, and the last 2bytes are port number. You should parse them to communicate with peers for pieces.
I think theory(specs) is probably not a fact. all specs about Bittorent from Internet become outdated. It might be impossible to check whether announce request is correct by just following the spec.
I recommend you to try using Wireshark. Capture packets that common Bittorent-client sends, and then check how common Bittornet client works. Compare announce request that your php code send, to one common client sends.
Actually, I'm trying to implement Bittorent client in C. I had a hard time communicating with a tracker server, since not specifying compact field in request message. But most specs says compact field in announce request is optional.
Try using wireshark. then compare two requests(your request and the other that common bittorent sends.)

Related

Why most push notifications fail to reach the device

Not sure if it's the same issue as iPhone SDK Push notification randomly fails . But some notifications do arrive, I do have does "unpack" warnings when upacking command 8 packets.
I have a php file that pulls any notifications from a table (DB) which has token, payload, and device (iphone,ipad). This php runs every 15 seconds on the server cron. I don't know why 20% of the notifications arrive sometimes.... sometimes 80% arrive.... and sometime none arrive (I have 10 devices for testing). I read that apples notifications are "best effort" and that there are not guaranteed to arrive but this is ridiculous, something must be wrong with the logic of my code. Here is my code, I have changed names of directories, DB info and files for privacy reasons. Please help.
<?php
include("/#####/DB.conf");
$link = mysql_connect($host,$userDB,$passDB) or die("Failed to connect: " . mysql_error());
mysql_select_db('#######') or die("Failed to connect: " . mysql_error());
mysql_query("SET NAMES 'utf8'");
$certFile_iphone = '/##########/push_production.pem';
$certFile_ipad = '/########/push_production.pem';
$passphrase = '#########';
$sent += sendPush($certFile_iphone,$passphrase,'iphone');
$borrados += checkFeedback($certFile_iphone,$passphrase,'iphone');
$sent += sendPush($certFile_ipad,$passphrase,'ipad');
$borrados += checkFeedback($certFile_ipad,$passphrase,'ipad');
//////////////////////////////
//Send notification to users//
//////////////////////////////
function sendPush($cert,$pass,$device)
{
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', $cert);
stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);
// Open a connection to the APNS server
$fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $err, $errstr, 600, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
if (!$fp)
exit("Failed to connect: $err $errstr" . PHP_EOL);
//gets all the devices of that type and their current notification to send
$query = mysql_query("SELECT * FROM pushNotification_buffer WHERE device = '".$device."' LIMIT 1000;");
$errors = 0;
$sent = 0;
$i = 0;
while($row = mysql_fetch_array($query))
{
$payload = $row['payload'];
$deviceToken = $row['identificador'];
//$msg = chr(0) . pack('n', 32) . pack('H*', $row['identificador']) . pack('n', strlen($payload)) . $payload;
$msgInner =
chr(1)
. pack('n', 32)
. pack('H*', $deviceToken)
. chr(2)
. pack('n', strlen($payload))
. $payload
. chr(3)
. pack('n', 4)
. chr($i)
. chr(4)
. pack('n', 4)
. pack('N', time() + 86400)
. chr(5)
. pack('n', 1)
. chr(10)
;
$msg=
chr(2)
. pack('N', strlen($msgInner))
. $msgInner
;
$i++;
$result = fwrite($fp, $msg, strlen($msg));
if (!$result)
{
$errors++;
usleep(1000000);
$result = fwrite($fp, $msg, strlen($msg));
error_log("Error on Device (2): " . $row['identificador']);
}
if (!$result)
{
$errors++;
fclose($fp);
return $sent;
}
else
{
$sent++;
mysql_query("DELETE FROM pushNotification_buffer WHERE identificador = '".$row['identificador']."' AND payload = '".mysql_real_escape_string($row['payload'])."' LIMIT 1;");
}
error_response($fp);
}
fclose($fp);
return $sent;
}
/////////////////
//APNS FeedBack//
/////////////////
function checkFeedback($cert,$pass,$device){
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', $cert);
stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);
$fp = stream_socket_client('ssl://feedback.push.apple.com:2196', $error, $errorString, 600, STREAM_CLIENT_CONNECT, $ctx);
$toDelete = 0;
if(!$fp) echo ("NOTICE: Failed to connect to device: $error - $errorString.");
while ($devcon = fread($fp, 38)){
$arr = unpack("H*", $devcon);
$rawhex = trim(implode("", $arr));
$token = substr($rawhex, 12, 64);
if(!empty($token))
{
if($device == 'iphone'){
mysql_query("DELETE FROM iphoneDevicesAPNS WHERE identificador = '" . $token . "' LIMIT 1;");
}else{
mysql_query("DELETE FROM ipadDevicesAPNS WHERE identificador = '" . $token . "' LIMIT 1;");
}
$toDelete++;
}
}
fclose($fp);
return $toDelete;
}
/////////////////////////////
//Error response, command 8//
/////////////////////////////
function error_response($fp){
$read = array($fp);
$null = null;
$changedStreams = stream_select($read, $null, $null, 0, 1000000);
if ($changedStreams === false)
{
echo ("Error: Unabled to wait for a stream availability");
}
elseif ($changedStreams > 0){
$responseBinary = fread($fp, 6);
if ($responseBinary !== false || strlen($responseBinary) == 6){
$response = unpack('Ccommand/Cstatus_code/Nidentifier', $responseBinary);
var_dump($response);
if (strlen($response["command"] > 0) || strlen($response["status_code"] > 0) || strlen($response["identifier"] > 0)){
$texto=
"------------------------------------------". "\n".date("Y-m-d G:i:s"). "\n";
"id: " . $response["identifier"] . "\n" .
"code: ". $response["status_code"] . "\n" .
"command: ". $response["command"] . "\n". "\n"
;
file_put_contents ( "/#####/logAPNS.log" ,$texto, FILE_APPEND);
}
}
}
}
?>
I do have does "unpack" warnings when upacking command 8 packets
This implies that you are getting error responses from Apple, but from what I see in the code, you don't do anything with these errors beside logging them. Each such error response means that you sent invalid data to Apple, and as a result Apple closed the connection. This means any message sent after the invalid one will be discarded until you open a new connection.
The proper way to handle such responses is to parse the bad message ID contained in them, open a new connection to APNS, and re-send all the messages that you sent after the bad message.
The most likely error is invalid device token. That usually happen when you send a development token to production push environment or vice versa. If you clear the invalid device tokens from your DB, you are likely to see 100% of the messages arriving.

PHP Communication with Smtp

I have a mail application that send about 5000 emails daily(a lot of account paperwork) via cron job, and works fine when the mail is sending to just one recipient. The problem comes when we activate the BCC copy, then the app starts sending until 980-1050 mails and start recieving 4.5.3 error from the smtp(Too many recipients). If i pause the job and run the cron again, the php process got a new pid, and start sending ok until it reach the same limit(980-1050 mails);
So my question is: is there a way to regenerate the php process id ?
If i was capable to do that, then the app would send those mails without problem.
Or maybe i'm missing some postfix configuration?
Relevant part of the code:
/**
* _Send_SmtpData
* Handles the SMTP negotiation for sending the email header and body.
*
* #param String $rcpt_to The 'receipt to' address to send the email to. This is a bare email address only.
* #param String $to The 'to' address to send this to. This can contain a name / email address in the standard format ("Name" <email#address>)
* #param String $subject The subject of the email to send.
* #param String $body The body of the email to send.
* #param String $headers The headers of the email to send.
**/
function _Send_SmtpData(&$rcpt_to, &$to, &$subject, &$body, &$headers)
{
$data = "DATA";
$this->DebugMemUsage('Trying to put ' . $data);
if (!$this->_Put_Smtp_Connection($data)) {
$this->ErrorCode = 12;
$this->ErrorCodeSMTPEnhanced = false;
$this->Error = GetLang('UnableToSendEmail_Data');
$this->_Close_Smtp_Connection();
$this->DebugMemUsage('Got error ' . $this->Error);
return array(false, $this->Error);
}
$response = $this->_get_response();
$this->DebugMemUsage('Got response ' . $response);
$responsecode = substr($response, 0, 3);
if ($responsecode != '354') {
$this->ErrorCode = $responsecode;
$this->ErrorCodeSMTPEnhanced = $this->_GetSMTPEnhancedErrorCode($response);
$this->Error = $response;
$this->_Close_Smtp_Connection();
$this->DebugMemUsage('Got error ' . $this->Error);
return array(false, $this->Error);
}
$msg = "To: " . $to . $this->_smtp_newline . "Subject: " . $subject . $this->_smtp_newline . $headers . $this->_smtp_newline . preg_replace('/^\.(\r|\n)/m', ' .${1}', $body);
$msg = str_replace("\r\n","\n",$msg);
$msg = str_replace("\r","\n",$msg);
$lines = explode("\n",$msg);
foreach ($lines as $no => $line) {
// we need to rtrim here so we don't get rid of tabs before the start of the line.
// the tab is extremely important for boundaries (eg sending multipart + attachment)
// so it needs to stay.
$data = rtrim($line);
$this->DebugMemUsage('Trying to put ' . $data);
if (!$this->_Put_Smtp_Connection($data)) {
$this->ErrorCode = 13;
$this->ErrorCodeSMTPEnhanced = false;
$this->Error = GetLang('UnableToSendEmail_DataWriting');
$this->_Close_Smtp_Connection();
$this->DebugMemUsage('Got error ' . $this->Error);
return array(false, $this->Error);
}
}
$data = $this->_smtp_newline . ".";
$this->DebugMemUsage('Trying to put ' . $data);
if (!$this->_Put_Smtp_Connection($data)) {
$this->ErrorCode = 14;
$this->ErrorCodeSMTPEnhanced = false;
$this->Error = GetLang('UnableToSendEmail_DataFinished');
$this->_Close_Smtp_Connection();
$this->DebugMemUsage('Got error ' . $this->Error);
return array(false, $this->Error);
}
$response = $this->_get_response();
$this->DebugMemUsage('Got response ' . $response);
$responsecode = substr($response, 0, 3);
if ($responsecode != '250') {
$this->ErrorCodeSMTPEnhanced = $this->_GetSMTPEnhancedErrorCode($response);
$this->ErrorCode = $responsecode;
$this->Error = $response;
$this->_Close_Smtp_Connection();
$this->DebugMemUsage('Got error ' . $this->Error);
return array(false, $this->Error);
}
$this->DebugMemUsage('Mail accepted ');
/**
* We got this far, this means we didn't encounter any errors.
* Cleanup previous error codes and variables since they are no longer relevant
* with the current process iteration.
*/
$this->Error = '';
$this->ErrorCode = false;
$this->ErrorCodeSMTPEnhanced = false;
$this->_smtp_email_count++;
return array(true, false);
}
In the end the "bug" was in another function, the bcc copy function; in every call for the working process, it was not cleaning the previous bcc copies of the mail, but sum them until they reach the limit.

Iphone Push Notification Wrong Device Token

Finally,I found out the sandbox tokens,which were mixed with the production tokens,and I update the code,receive the error response from apple,when post a sandbox token to apple,the next tokens all failed....While the push msg was "$payload['aps'] = array('content-available' => 1); ",apple will push the msg ,but the device will not show this msg,when I find the wrong one,I delete it,and retest.
There is an amusing problem of apple push notification service:
when I generate a post notification for my device and send to apple's server,for a while,my device will receive the notification.
But if I first post a notification by a wrong devicetoken,then post a right notification to my device,my device will be responseless,just like the push notification was eaten by apple?
What wrong with this?
I use php code for push service,here is the most important part:
$payload['aps'] = array('content-available' => 1);
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
foreach ($line as $deviceToken) {
break;
}
print_r($deviceToken);
$apnsMessage = chr(1) . $time . $time . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $deviceToken)) . chr(0) . chr(strlen($payload)) . $payload;
$ret = fwrite($apns, $apnsMessage);
if ($ret !== strlen($apnsMessage)) {
echo "push error";
}
ob_flush();
flush();
$read = array($apns);
$null = null;
$changedStreams = stream_select($read, $null, $null, 0, 100000);//wait for 0.1s
if ($changedStreams === false) {
echo "Error: Unabled to wait for a stream availability";
} elseif ($changedStreams > 0) {
print "can't receive any notification behind this token";
$responseBinary = fread($apns, 6);
if ($responseBinary !== false || strlen($responseBinary) == 6) {
$response = unpack('Ccommand/Cstatus_code/Nidentifier', $responseBinary);
print_r($response);
}
} else {
//update the success push status in database
}
print "<br>";
}
I dont know what's wrong with this,and I have also add feedback service in another php,and get none wrong token from apple,Because I have many devicetoken in my database,I cant find which one of them is wrong.
Someone help me,forgive my poor english
Assuming your $apns is implemented in this way
$apns = stream_socket_client('ssl://' . $apnsHost . ':' . $apnsPort, $error, $errorString, 2,STREAM_CLIENT_CONNECT, $streamContext);
you should check the $errorString to find out the error.
or try to close the socket for each message and see if there is any difference
fwrite($apns, $apnsMessage);
socket_close($apns);
fclose($apns);

Apache thrift: client timeout issues

I have some Apache Thrift (v.0.6.1) test application with perl-server and php-client.
The behaviour I cannot explain: If we call server-method with invalid argument we see the error in server-output, but php-client stays waiting the response infinitely.
Here are the sources of server:
sub new {
my $classname = shift;
my $self = {};
return bless($self,$classname);
}
sub DateToTimestamp
{
my ($self, $date) = #_;
my $result = CommonAPI::DateToTimestamp($date);
return $result;
}
eval {
my $handler = new RPCHandler;
my $processor = new RPCPerformanceTest::RPCPerformanceTestProcessor($handler);
my $serversocket = new Thrift::ServerSocket(9091);
my $forkingserver = new Thrift::ForkingServer($processor, $serversocket);
print "Starting the server...\n";
$forkingserver->serve();
print "done.\n";
}; if ($#) {
if ($# =~ m/TException/ and exists $#->{message}) {
my $message = $#->{message};
my $code = $#->{code};
my $out = $code . ':' . $message;
die $out;
} else {
die $#;
}
}
and client:
try {
$socket = new TSocket($server_host, $server_port);
$transport = new TBufferedTransport($socket, 1024, 1024);
$protocol = new TBinaryProtocol($transport);
$client = new RPCPerformanceTestClient($protocol);
$transport->open();
$start = microtime(true);
$result = $client->DateToTimestamp('071/26/2011 01:23:45');
var_dump($result);
} catch (Exception $e) {
echo 'Exception: <b>' . $e->getMessage() . '</b>';
}
Why is this happening? Is it my fault? Is it expected behavour?
The Thrift PHP library is a bit broken. You need to manually set the timeouts
E.g.
$socket = new TSocket('host', 9095);
$socket->setSendTimeout(60000);
$socket->setRecvTimeout(60000)
This happens often with protocols that do not supply message length: a client sends more data then the server expects and waits for the server to receive the data. The server receives some of the data, tries to parse it and fails. Now the server-side of the protocol is in errorneous state. If it continues to read the data, it may block. Most probably, the server-side has sent you some error response and is waiting at the same time for the client to receive the response, but that will never happen too.
This is my guess. The best strategy IMHO is to set a time-out for both client and server sockets.

All of a sudden, my Amazon S3 HTTP requests aren't working. Signature Does Not Match error. Help?

My code was working just fine a couple days ago. Then, all of a sudden, BAM: it stopped working. My PUTs stopped going through, with a SignatureDoesNotMatch error. Help?
require_once 'Crypt/HMAC.php';
require_once 'HTTP/Request.php';
function uploadFile($path_to_file, $store_file_as, $bucket, $debugmode = false) {
$S3_URL = "http://s3.amazonaws.com/";
$filePath = $path_to_file;
$contentType = 'audio/mpeg';
$keyId = 'THISISMYKEY, YES I DOUBLE CHECKED IT';
$secretKey = 'THIS IS MYSECRET, YES I DOUBLED CHECKED IT';
$key = $store_file_as;
$resource = $bucket . "/" . $key;
$acl = "public-read";
$verb = "PUT";
$httpDate = gmdate("D, d M Y H:i:s T");
$stringToSign = "PUT\n\naudio/mpeg\n$httpDate\nx-amz-acl:$acl\n/$resource";
$hasher =& new Crypt_HMAC($secretKey, "sha1");
$str = $hasher->hash($stringToSign);
$raw = '';
for ($i=0; $i < strlen($str); $i+=2) {
$raw .= chr(hexdec(substr($str, $i, 2)));
}
$signature = base64_encode($raw);
$req =& new HTTP_Request($S3_URL . $resource);
$req->setMethod('PUT');
$req->addHeader("content-type", $contentType);
$req->addHeader("Date", $httpDate);
$req->addHeader("x-amz-acl", $acl);
$req->addHeader("Authorization", "AWS " . $keyId . ":" . $signature);
$req->setBody(file_get_contents($filePath));
$req->sendRequest();
echo $req->getResponseBody();
}
Run your signature against the Amazon S3 JavaScript signature tester.
If the two signatures do not match, you know something is wrong with the keys or signing procedure.
If the two match, your keys and signing process are correct and the problem is somewhere else.
The JS tester is invaluable for troubleshooting signature generation problems.

Categories