Reputation: 2064
Update #4: Demo java snippet added for working with UDP & sending announce msg (remember connect is first!) check own response bellow.
====================================================
Update #3: I managed to make it work, method doConnect() presented bellow is OK, more info in my own response bellow.
====================================================
I am mainly interested in how to download a tracker response when protocol of announce url is UDP.
Details: So these are some announce urls from a valid torrent file (first one is the main one)
http://tracker.torrentbox.com:2710/announce
udp://elbitz.net:80/announce.php?passkey=362fc69de3402e8ef5794c7ecf7c58d4
udp://tracker.podtropolis.com:2711/announce
If protocol is HTTP every thing goes well & this is how i work:
String fullUrl = announceURL + "?info_hash=" + this.m_TorrentInfoHashAsURL + .. // i add the params
URL url = new URL(fullUrl);
URLConnection connection = url.openConnection();
InputStream is = connection.getInputStream();
.. //reading the stream
If protocol is UDP, the URL constructor throws a "java.net.MalformedURLException: unknown protocol: udp"
So i guess problem can be resumed to the following: how do I download a resounce from a URL on UDP protocol? (hope it simple & i see no Datagrams stuff)
UPDATE #1:
I did some more investigations online & arrived at the following structure pasted below (should work..but doesnt, i mean locally it does, but not with real tracker)
link to specs: http://www.bittorrent.org/beps/bep_0015.html
Ex: This is how i create the socket, but on valid tracker i never receive nothing back as response so something aint working:
if full url: udp://elbitz.net:80/announce.php?passkey=362fc69de3402e8ef5794c7ecf7c58d4
this.m_TrackerHost: elbitz.net
this.m_TrackerPort: 80
private DatagramSocket m_WorkingSocket;
private DatagramSocket getWorkingSocket() {
Logger.d(TAG, "getWorkingSocket()");
if(this.m_WorkingSocket==null){
Random rnd = new Random();
for (int i = 0; i < 100; i++) {
try {
int port = 15000 + rnd.nextInt(15000); // [15000-30000)
DatagramSocket result = new DatagramSocket(port);
InetAddress trackerAddress = InetAddress.getByName(this.m_TrackerHost);
result.connect(trackerAddress, this.m_TrackerPort);
this.m_WorkingSocket = result;
} catch (SocketException se) {
Logger.w(TAG, "getWorkingSocket() - port is taken");
} catch (SecurityException se) {
Logger.w(TAG, "getWorkingSocket() - port is blocked?");
} catch (UnknownHostException e) {
Logger.w(TAG, "getWorkingSocket() - unkwnown host?");
}
}
}
return this.m_WorkingSocket;
}
& now full code from doConnect which should be the first comunication phase (next is announce .. similar code there)
private boolean doConnect() throws IOException{
Logger.d(TAG, "doConnect()");
DatagramSocket workingSocket = this.getWorkingSocket();
DatagramPacket sendPacket = null, receivePacket = null;
byte[] sendData = null;
byte[] receiveData = null;
int round = 0;
Logger.d(TAG, "doConnect(): first round, timeout: " + this.getTimeoutInMillis(ACTION_ID_CONNECT, round));
while(true) {
if(round==8){
Logger.w(TAG, "doConnect() - failed to connect with tracker, consumed in vain all 8 rounds..");
return false;
}
workingSocket.setSoTimeout(this.getTimeoutInMillis(ACTION_ID_CONNECT, round));
if(receivePacket==null){
/*
Offset Size Name Value
0 32-bit integer action 0 // connect
4 32-bit integer transaction_id
8 64-bit integer connection_id
16 */
receiveData = new byte[16]; //CONNECT: at least 16 bytes
receivePacket = new DatagramPacket(receiveData, receiveData.length);
sendData = this.getConnectRequest();//return byte[] with everything..just like in specs
sendPacket = new DatagramPacket(sendData, sendData.length);
}
try {
Logger.d(TAG, "doConnect() - sending connect data: " + (Arrays.toString(sendData)));
workingSocket.send(sendPacket);
workingSocket.receive(receivePacket);
break;
} catch (SocketTimeoutException ste) {
round ++;
Logger.w(TAG, "doConnect() connect - new round: " + (round+1) + "|timeout: " + this.getTimeoutInMillis(ACTION_ID_CONNECT, round));
continue;
}
}
byte[] connectResponse = receivePacket.getData();
int actionIdFromResponse = Utils.byteArrayToInt(Utils.subArray(connectResponse, 0, 4));
int transactionIdFromResponse = Utils.byteArrayToInt(Utils.subArray(connectResponse, 4, 4));
long connectionIdFromResponse = Utils.byteArrayToLong(Utils.subArray(connectResponse, 8, 8));
if(transactionIdFromResponse!=this.m_TransactionId){
Logger.w(TAG, "doConnect() - received different transactionId");
return false;
}
if(actionIdFromResponse!=ACTION_ID_CONNECT){
Logger.w(TAG, "doConnect() - didnt received ACTION_ID_CONNECT");
return false;
}
//store connectionId
this.m_ConnectionId = connectionIdFromResponse;
return true;
}
Problem remains.. i never receive a response from tracker (tried with other url too) also New question: is it OK to create socket on elbitz.net, port: 80, when full url contains more info (ex: /announce) ?
Update #2
Code above seems to work OK.. i found on google a list of trackers that have implemented this spec & voila response happened (ex: "udp://tracker.openbittorrent.com:80")
New question & again spec is here: http://www.bittorrent.org/beps/bep_0015.html - i dont seem to see how do i get a list of peers ?? .. in the normal request to a torrent tracker (by http), there were 2 cases: normal response (a bencoded map) & compacted response (in binary form). So were is the list of peers now ?
/* Offset Size Name Value 0 32-bit integer action 1 // announce 4 32-bit integer transaction_id 8 32-bit integer interval 12 32-bit integer leechers 16 32-bit integer seeders 20 + 6 * n 32-bit integer IP address 24 + 6 * n 16-bit integer TCP port 20 + 6 * N */
from my tests i allways receive same values for the fields: IP address & TCP port .. plus that i get one response per request ..so this CANT BE IT!.. i need a list of peers!
Please help! the only types of response message that i havent implemented yet are scrape & error... but no one contains info of interest to me (peer info: ip & port) ex: scrape
Offset Size Name Value
0 32-bit integer action 2 // scrape
4 32-bit integer transaction_id
8 + 12 * n 32-bit integer seeders
12 + 12 * n 32-bit integer completed
16 + 12 * n 32-bit integer leechers
8 + 12 * N
Upvotes: 5
Views: 5131
Reputation: 2064
My responses to my own question.. well thanks, me!
byteBuffer
.
Again the real info is here: http://www.bittorrent.org/beps/bep_0015.html;transactionId
, read specs)
my doConnect()
is Ok, I changed very few lines from it (ex: I'm using max size in bytes for the byteBuffer
used
by DataGramPacket
, meaning new byte[65508] instead of [16] - the min size. When receiving a
response the rest will be padding bytes, meaning 0's).doConnect
there is a while-loop (it shows the max 8 rounds rule);socketTimeOut
is continually increasing (using formula from specs, 30 sec, 90.. etc).receive()
) & I have it on own thread (usually those trackers that respond don't need to consume more than 2 rounds.. that is nice).UPDATE***
DatagramSocket workingSocket = ?;//
DatagramPacket sendPacket = null, receivePacket = null;
byte[] sendData = null;
byte[] receiveData = null;
receiveData = new byte[65508]; //NOTE: at least 16 bytes | 65508 is max size, unused bytes are 0
receivePacket = new DatagramPacket(receiveData, receiveData.length);
sendData = this.getRequestTypeAnnounce(announceUDPWrapper.a_TransactionId);
sendPacket = new DatagramPacket(sendData, sendData.length);
workingSocket.send(sendPacket);
workingSocket.receive(receivePacket);
byte[] fullResponse = receivePacket.getData();
private byte[] getRequestTypeAnnounce(AnnounceUDPWrapper announceUDPWrapper) {
//long connectionId, int transactionId, int tcpPort
ByteBuffer bBuffer = ByteBuffer.allocate(98);
bBuffer.putLong(announceUDPWrapper.a_ConnectionId);
bBuffer.putInt(ACTION_ID_ANNOUNCE);
bBuffer.putInt(announceUDPWrapper.a_TransactionId);
bBuffer.put(announceUDPWrapper.a_InfoHash);//<<<< what you asked.. adding the infoHash which is byte[]
bBuffer.put(this.m_MyId);
bBuffer.putLong(announceUDPWrapper.a_Downloaded);
bBuffer.putLong(announceUDPWrapper.a_Uploaded);
bBuffer.putLong(announceUDPWrapper.a_Left);
bBuffer.putInt(announceUDPWrapper.a_EventType.ordinal());
bBuffer.put(Utils.intToByteArray(0));// ip, 0 = default
bBuffer.putInt(0);//key
bBuffer.putInt(10);//num_want
byte[] portAsBytes = Utils.intToByteArray(announceUDPWrapper.a_ListeningTCPPort);
bBuffer.put(Utils.subArray(portAsBytes, 2, 2)); //port
return bBuffer.array();
}
Upvotes: 4
Reputation: 4044
Seems to me you are mixing network layers here.
udp is on a lower layer - http:// goes over tcp, whereas some other protocols can go over udp. IP is below that, so the protocol you are trying to use is something like "torrent over udp, over IP".
See here for some torrent/udp protocols documentation.
Upvotes: 0