package the8472.mldht.indexing;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Collection;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TransferQueue;
import lbms.plugins.mldht.kad.DHT;
import lbms.plugins.mldht.kad.messages.GetPeersRequest;
import lbms.plugins.mldht.kad.messages.MessageBase;
import lbms.plugins.mldht.kad.messages.MessageBase.Method;
import the8472.mldht.Component;
import the8472.utils.ConfigReader;
public class OpentrackerLiveSync implements Component {
TransferQueue<ByteBuffer> toSend = new LinkedTransferQueue<>();
DatagramChannel channel;
byte[] id = new byte[4];
Thread t = new Thread(this::send);
volatile boolean running = true;
private static final int HEADER_LENGTH = 0x08;
private static final int PEER_LENGTH = 0x1C;
private static final int PEERS_PER_PACKET = 50;
public OpentrackerLiveSync() {
ThreadLocalRandom.current().nextBytes(id);
}
@Override
public void start(Collection<DHT> dhts, ConfigReader config) {
try {
channel = DatagramChannel.open(StandardProtocolFamily.INET);
channel.setOption(StandardSocketOptions.IP_MULTICAST_TTL, 1);
channel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
// we only need to send, not to receive, so need to bind to a specific port
channel.bind(new InetSocketAddress(0));
channel.connect(new InetSocketAddress(InetAddress.getByAddress(new byte[] {(byte) 224,0,23,5}), 9696));
} catch (IOException e) {
e.printStackTrace();
return;
}
t.setDaemon(true);
t.setName("opentracker-sync");
t.start();
// OT-sync only supports ipv4 atm
dhts.stream().filter(d -> d.getType().PREFERRED_ADDRESS_TYPE == Inet4Address.class).forEach(d -> {
d.addIncomingMessageListener(this::incomingPacket);
});
}
void incomingPacket(DHT dht, MessageBase msg) {
if(!running)
return;
if(msg.getType() != MessageBase.Type.REQ_MSG || msg.getMethod() != Method.GET_PEERS)
return;
GetPeersRequest req = (GetPeersRequest) msg;
ByteBuffer buf = ByteBuffer.allocate(PEER_LENGTH);
buf.put(req.getTarget().getHash());
buf.put(req.getOrigin().getAddress().getAddress());
buf.putShort((short) req.getOrigin().getPort());
buf.putShort((short) 0);
buf.flip();
toSend.add(buf);
}
void send() {
ByteBuffer sendBuffer = ByteBuffer.allocate(HEADER_LENGTH);
sendBuffer.put(id);
sendBuffer.put(new byte[4]);
sendBuffer.flip();
ByteBuffer[] buffers = new ByteBuffer[1 + PEERS_PER_PACKET];
buffers[0] = sendBuffer;
try {
while(running) {
for(int i = 1;i<buffers.length;i++) {
buffers[i] = toSend.take();
}
channel.write(buffers);
buffers[0].rewind();
}
} catch (IOException | InterruptedException e) {
running = false;
e.printStackTrace();
}
}
@Override
public void stop() {
running = false;
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// === OT format
// [mcast address seems to be outdated]
/*
Syncing is done as udp packets in the multicast domain 224.0.42.5 port 9696
Each tracker should join the multicast group and send its live sync packets
to that group, using a ttl of 1
Format of all sync packets is straight forward, packet type determines
which kind of packet this is:
0x0000 0x04 id of tracker instance
0x0004 0x04 packet type
########
######## PEER SYNC PROTOCOL ########
########
Each tracker instance accumulates announce requests until its buffer is
full or a timeout is reached. Then it broadcasts its live sync packer:
packet type SYNC_LIVE
[ 0x0008 0x14 info_hash
0x001c 0x04 peer's ipv4 address
0x0020 0x02 peer's port
0x0024 0x02 peer flags v1 ( SEEDING = 0x80, COMPLETE = 0x40, STOPPED = 0x20 )
]*
*/