Java UDP create connection request with tracker never returns a response

60 views Asked by At

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.

1

There are 1 answers

0
DrBrad On

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