javaudptorrent

Java UDP create connection request with tracker never returns a response


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.


Solution

  • To use a UDP tracker you must do the following: Connect, Announce. Scrape is used for getting data on multiple sample hashes.

    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