Im implementing an app for downloading torrent from .torrent files as a study project for CLI tools and native applications using GraalVM. Im having problems when trying to create a connection with a UDP tracker. Im able to send the connection request but the receive request is ranging forever. I researched everywhere but i couldn’t find why it’s not working. This is the java class responsible for connecting to the UDP tracker:
package org.jtorr.service.impl;
import com.dampcake.bencode.Bencode;
import org.jtorr.component.generator.TransactionIdGenerator;
import org.jtorr.exception.TrackerServiceException;
import org.jtorr.model.bencode.BencodeData;
import org.jtorr.model.bencode.TrackerResponse;
import org.jtorr.model.tracker.TrackerUDPConnect;
import org.jtorr.service.TrackerService;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
public class UdpTrackerServiceImpl implements TrackerService {
private static final int DEFAULT_PORT = 6969;
private final DatagramSocket datagramSocket;
private final TransactionIdGenerator transactionIdGenerator;
private final Bencode bencode;
public UdpTrackerServiceImpl() {
try {
datagramSocket = new DatagramSocket(DEFAULT_PORT);
transactionIdGenerator = new TransactionIdGenerator();
bencode = new Bencode();
} catch (SocketException e) {
throw new TrackerServiceException("Error instantiating service: " + e.getMessage(), e);
}
}
@Override
public TrackerResponse getPeersInfoFromTracker(BencodeData bencodeData, String peerId, String infoHash) {
var uri = announceToURI(bencodeData.announce());
createConnectionRequest(uri);
return null;
}
private URI announceToURI(String announce) {
try {
return new URI(announce);
} catch (URISyntaxException e) {
throw new TrackerServiceException("Error creating URI from announce: " + e.getMessage(), e);
}
}
private void createConnectionRequest(URI uri) {
try {
var transactionId = transactionIdGenerator.generate();
var buff = TrackerUDPConnect.builder()
.transactionId(transactionId)
.build()
.getBytes();
var address = InetAddress.getByName(uri.getHost());
var packet = new DatagramPacket(buff, buff.length, address, uri.getPort());
datagramSocket.send(packet);
packet = new DatagramPacket(buff, buff.length);
datagramSocket.receive(packet);
var response = new String(packet.getData(), 0, packet.getLength());
System.out.println(response);
} catch (UnknownHostException e) {
throw new TrackerServiceException("Error connecting to host: " + e.getMessage(), e);
} catch (IOException e) {
throw new TrackerServiceException("Error creating connection with tracker: " + e.getMessage(), e);
}
}
}
I’ve thought that maybe ubuntu was blocking the UDP connection somehow. I’ve tried freeing up the port by doing the following: sudo ufw allow 38438/udp
.
But that had no effect in the problem.
I`ve checked to see if it was a firewall problem but firewall is disabled in ubuntu.
To use a UDP tracker you must do the following: Connect, Announce. Scrape is used for getting data on multiple sample hashes.
I recommend that you use the same UDP socket for all UDP trackers as it would save bandwidth and ports, typical with other programs like Transmission.
Note that if you fail to send what I have shown the tracker will likely never respond... Really annoying for debugging...
As of BEP-15 specifications the connection request should look like this - every number is Big Endian.
Offset Size Name Value
0 64-bit integer protocol_id 0x41727101980 // magic constant
8 32-bit integer action 0 // connect
12 32-bit integer transaction_id
16
Example of possible actions
Connect: 0
Announce: 1
Scrape: 2
Error: 3
Java Impl
byte[] PROTOCOL_ID = { 0x00, 0x00, 0x04, 0x17, 0x27, 0x10, 0x19, (byte) 0x80 };//-9216317402361102336l;
byte[] buf = new byte[16];
int action = 0;
System.arraycopy(PROTOCOL_ID, 0, buf, 0, PROTOCOL_ID.length);
buf[8] = ((byte) action);
buf[9] = ((byte) (action >> 8));
buf[10] = ((byte) (action >> 16));
buf[11] = ((byte) (action >> 24));
System.arraycopy(tid, 0, buf, 12, tid.length);
And you should receive something like this
Offset Size Name Value
0 32-bit integer action 0 // connect
4 32-bit integer transaction_id
8 64-bit integer connection_id
16
Java Impl
int action = ((buf[0] & 0xff) << 24) |
((buf[1] & 0xff) << 16) |
((buf[2] & 0xff) << 8) |
(buf[3] & 0xff);
byte[] tid = new byte[4];
System.arraycopy(buf, 4, tid, 0, tid.length);
connectionID = (((long) (buf[off] & 0xff) << 56) |
((long) (buf[off+1] & 0xff) << 48) |
((long) (buf[off+2] & 0xff) << 40) |
((long) (buf[off+3] & 0xff) << 32) |
((long) (buf[off+4] & 0xff) << 24) |
((long) (buf[off+5] & 0xff) << 16) |
((long) (buf[off+6] & 0xff) << 8) |
((long) (buf[off+7] & 0xff)));
Now for the announce, now that you have the connection ID.
Announce request as of BEP-15
Offset Size Name Value
0 64-bit integer connection_id
8 32-bit integer action 1 // announce
12 32-bit integer transaction_id
16 20-byte string info_hash
36 20-byte string peer_id
56 64-bit integer downloaded
64 64-bit integer left
72 64-bit integer uploaded
80 32-bit integer event 0 // 0: none; 1: completed; 2: started; 3: stopped
84 32-bit integer IP address 0 // default
88 32-bit integer key
92 32-bit integer num_want -1 // default
96 16-bit integer port
98
Java Impl
byte[] buf = new byte[98];
//System.arraycopy(connectionID, 0, buf, 0, connectionID.length);
buf[0] = ((byte) (connectionID >> 56));
buf[1] = ((byte) (connectionID >> 48));
buf[2] = ((byte) (connectionID >> 40));
buf[3] = ((byte) (connectionID >> 32));
buf[4] = ((byte) (connectionID >> 24));
buf[5] = ((byte) (connectionID >> 16));
buf[6] = ((byte) (connectionID >> 8));
buf[7] = ((byte) connectionID);
buf[8] = ((byte) (action.getCode() >> 24));
buf[9] = ((byte) (action.getCode() >> 16));
buf[10] = ((byte) (action.getCode() >> 8));
buf[11] = ((byte) action.getCode());
System.arraycopy(tid, 0, buf, 12, tid.length);
System.arraycopy(infoHash, 0, buf, 16, infoHash.length);
System.arraycopy(peerID, 0, buf, 36, peerID.length);
//DOWNLOADED
buf[56] = ((byte) (downloaded >> 56));
buf[57] = ((byte) (downloaded >> 48));
buf[58] = ((byte) (downloaded >> 40));
buf[59] = ((byte) (downloaded >> 32));
buf[60] = ((byte) (downloaded >> 24));
buf[61] = ((byte) (downloaded >> 16));
buf[62] = ((byte) (downloaded >> 8));
buf[63] = ((byte) downloaded);
//LEFT
//UPLOADED
buf[72] = ((byte) (uploaded >> 56));
buf[73] = ((byte) (uploaded >> 48));
buf[74] = ((byte) (uploaded >> 40));
buf[75] = ((byte) (uploaded >> 32));
buf[76] = ((byte) (uploaded >> 24));
buf[77] = ((byte) (uploaded >> 16));
buf[78] = ((byte) (uploaded >> 8));
buf[79] = ((byte) uploaded);
//EVENT
buf[80] = ((byte) (event.getCode() >> 24));
buf[81] = ((byte) (event.getCode() >> 16));
buf[82] = ((byte) (event.getCode() >> 8));
buf[83] = ((byte) event.getCode());
//ADDRESS ( 0 for using packet origin - specify for different return )
buf[84] = ((byte) (address >> 24));
buf[85] = ((byte) (address >> 16));
buf[86] = ((byte) (address >> 8));
buf[87] = ((byte) address);
//KEY ( RANDOMLY GENERATED - PER REQUEST )
buf[88] = ((byte) (key >> 24));
buf[89] = ((byte) (key >> 16));
buf[90] = ((byte) (key >> 8));
buf[91] = ((byte) key);
//NUM WANT ( NUMBER OF PEERS WE WANT - DEFAULT -1)
buf[92] = ((byte) (numWant >> 24));
buf[93] = ((byte) (numWant >> 16));
buf[94] = ((byte) (numWant >> 8));
buf[95] = ((byte) numWant);
//PORT
buf[96] = (byte) ((port & 0xff00) >> 8);
buf[97] = (byte) (port & 0xff);
And finally for the Announce response.
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
Java Impl
int interval = (((buf[off] & 0xff) << 24) |
((buf[off+1] & 0xff) << 16) |
((buf[off+2] & 0xff) << 8) |
(buf[off+3] & 0xff));
int leechers = (((buf[off+4] & 0xff) << 24) |
((buf[off+5] & 0xff) << 16) |
((buf[off+6] & 0xff) << 8) |
(buf[off+7] & 0xff));
int seeders = (((buf[off+8] & 0xff) << 24) |
((buf[off+9] & 0xff) << 16) |
((buf[off+10] & 0xff) << 8) |
(buf[off+11] & 0xff));
byte[] addr;
if(origin.getAddress() instanceof Inet4Address){
addr = new byte[6];
}else{
addr = new byte[18];
}
int position = off+12;
while(position < len){
System.arraycopy(buf, position, addr, 0, addr.length);
peers.add(PeerUtils.unpackAddress(addr));
position += addr.length;
}
For more details take a look at my project: https://github.com/DrBrad/JTorrent