Compare commits
48 Commits
zzzot-0.18
...
zzzot-0.20
Author | SHA1 | Date | |
---|---|---|---|
cf48fcd901 | |||
8ff48800d6 | |||
1a90be6701 | |||
a4729aba16 | |||
cd2441ca4a | |||
d9d3283b3e | |||
1acaef9514 | |||
0b6d22da3b | |||
38c47f9993 | |||
6e7406567b | |||
b970d198ca | |||
241b95bc65 | |||
ee39fa92ae | |||
26ad8bfd08 | |||
8d82d13d1c | |||
d9b4a5e1c4 | |||
886a05730c | |||
65dd1161ed | |||
0edade465c | |||
1e94a95a7a | |||
b7009e3a20 | |||
9eece7ab8b | |||
f5ed7ec874 | |||
071a36c348 | |||
d07780615c | |||
e157028db4 | |||
8ff2c93b0a | |||
20245a3e8e | |||
edbb803095 | |||
b2432833e3 | |||
6073e61ba4 | |||
e1e29a953b | |||
059f1cf6e7 | |||
32062dff20 | |||
7a6c0bcccd | |||
1853bbfd0e | |||
5828c358f6 | |||
017d8a61c3 | |||
cc640088c4 | |||
2016546fc1 | |||
9ed1a1ce0c | |||
8fd17dd938 | |||
51e4184ea8 | |||
df683fcf5c | |||
ca66c075bf | |||
f35827abcb | |||
290b7e9ba8 | |||
9a988e5c6d |
12
CHANGES.txt
12
CHANGES.txt
@ -1,3 +1,15 @@
|
||||
2025-xx-xx [0.20.0] (Requires I2P 2.9.0 or higher)
|
||||
- Support UDP announces
|
||||
- Fix dup ids in jetty.xml, existing installs must fix manually,
|
||||
s/<Ref id=/<Ref refid=/g
|
||||
- Add interval to stats page
|
||||
- Add stats to I2P stats subsystem
|
||||
- Remove ElGamal support
|
||||
|
||||
2024-04-07 [0.19.0]
|
||||
- Disable full scrape by default
|
||||
- Handle BiglyBT scrape URLs
|
||||
|
||||
2020-08-30 [0.18.0]
|
||||
- Enable both encryption types
|
||||
- Disable pack200
|
||||
|
23
README.txt
23
README.txt
@ -2,7 +2,7 @@ ZzzOT I2P Open Tracker Plugin
|
||||
-----------------------------
|
||||
|
||||
This is a very simple in-memory open tracker, wrapped into an I2P plugin.
|
||||
A compiled version of the plugin is available at http://stats.i2p/i2p/plugins/
|
||||
Plugin su3 binaries are available at http://stats.i2p/i2p/plugins/
|
||||
|
||||
The plugin starts a new http server tunnel, eepsite, and Jetty server running at
|
||||
port 7662. The tracker status is available at http://127.0.0.1:7662/tracker/
|
||||
@ -11,15 +11,20 @@ If other files are desired on the eepsite, they can be added at eepsite/docroot
|
||||
The open tracker code and jsps were written from scratch, but depend on some
|
||||
code in i2psnark.jar from the I2P installation for bencoding, and of course on
|
||||
other i2p libraries. See the license files in I2P for i2p and i2psnark licenses.
|
||||
There is also some code modified from Jetty 5.1.15. See LICENSES.txt for the
|
||||
There is also some code modified from Jetty. See LICENSES.txt for the
|
||||
zzzot and Jetty licenses.
|
||||
|
||||
I2P source must be installed and built in ../i2p.i2p to compile this package.
|
||||
|
||||
Sure, as a standalone program in its own JVM with Jetty, this would be a pig -
|
||||
you should use the C opentracker instead. But since you're already running the
|
||||
JVM and Jetty, running this in the same JVM probably doesn't hog to much more
|
||||
memory.
|
||||
As of release 0.19.0:
|
||||
- Full scrape is disabled by default
|
||||
|
||||
As of release 0.20.0:
|
||||
- I2P 2.9.0 or higher required to build and run
|
||||
- UDP announces are supported, see http://i2p-projekt.i2p/spec/proposals/160
|
||||
- Non-compact responses are no longer supported
|
||||
- Seedless support is removed
|
||||
- Memory usage greatly reduced
|
||||
|
||||
Valid announce URLs:
|
||||
/a
|
||||
@ -31,6 +36,9 @@ Valid announce URLs:
|
||||
/tracker/announce.jsp
|
||||
/tracker/announce.php
|
||||
|
||||
UDP announce URLs (default port is 6969):
|
||||
udp://yourb32string.b32.i2p:6969/
|
||||
|
||||
Valid scrape URLs:
|
||||
/scrape
|
||||
/scrape.jsp
|
||||
@ -39,8 +47,5 @@ Valid scrape URLs:
|
||||
/tracker/scrape.jsp
|
||||
/tracker/scrape.php
|
||||
|
||||
The tracker also responds to seedless queries at:
|
||||
/Seedless/index.jsp
|
||||
|
||||
You may use the rest of the eepsite for other purposes; for example you
|
||||
may place torrent files in: eepsite/docroot/torrents/
|
||||
|
4
TODO.txt
4
TODO.txt
@ -1,11 +1,9 @@
|
||||
Configuration file:
|
||||
- clean time
|
||||
- max peers in response
|
||||
- disable full scrapes
|
||||
- disable all scrapes
|
||||
- disable seedless
|
||||
|
||||
Stop the cleaner
|
||||
Remove seedless
|
||||
|
||||
Throttles:
|
||||
- full scrapes
|
||||
|
@ -16,7 +16,8 @@
|
||||
<delete dir="plugin/eepsite/docroot/torrents/" />
|
||||
<!-- get version number -->
|
||||
<buildnumber file="scripts/build.number" />
|
||||
<property name="release.number" value="0.18.0" />
|
||||
<!-- NOTE: Change VERSION in ZzzOTController when you change this -->
|
||||
<property name="release.number" value="0.19.0" />
|
||||
|
||||
<!-- make the update xpi2p -->
|
||||
<!-- this contains everything except i2ptunnel.config -->
|
||||
@ -33,11 +34,14 @@
|
||||
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
|
||||
<arg value="version=${release.number}-b${build.number}" />
|
||||
</exec>
|
||||
<mkdir dir="plugin/lib/" />
|
||||
<exec executable="pack200" failonerror="true">
|
||||
<arg value="-r" />
|
||||
<arg value="plugin/lib/zzzot.jar" />
|
||||
<arg value="src/build/zzzot.jar" />
|
||||
</exec>
|
||||
<mkdir dir="plugin/eepsite/webapps/" />
|
||||
<mkdir dir="plugin/eepsite/logs/" />
|
||||
<exec executable="pack200" failonerror="true">
|
||||
<arg value="-r" />
|
||||
<arg value="plugin/eepsite/webapps/tracker.war" />
|
||||
|
@ -357,8 +357,8 @@
|
||||
<h2>Welcome to the ZzzOT I2P Plugin!</h2>
|
||||
<hr class="heading">
|
||||
<p class="warn" id="docroot">This help file is located at: <code>$PLUGIN/eepsite/docroot/help.html</code><br><span class="emphasis"><b>You should probably move it outside of the document root before you announce your eepsite as it may contain your username.</b></span></p>
|
||||
<p>ZzzOT is a simple in-memory BitTorrent open <a href="https://en.wikipedia.org/wiki/BitTorrent_tracker" class="external" target="_blank">tracker</a>, wrapped into an I2P plugin. The software depends on several I2P libraries, in addition to <code>i2psnark.jar</code> from the I2P installation for <a href="https://en.wikipedia.org/wiki/Bencode" class="external" target="_blank">bencoding</a>. Please report bugs on <a href="http://trac.i2p2.i2p/" target="_blank">trac.i2p2.i2p</a> or add comments and feature requests on <a href="http://zzz.i2p/forums/16" target="_blank">the plugin forum</a> on zzz.i2p.</p>
|
||||
<p>Source code is available (under the <a href="https://www.apache.org/licenses/LICENSE-2.0.html" class="external" target="_blank">Apache 2.0 license</a>) via the <code>i2p.plugins.zzzot</code> branch in the <a href="https://geti2p.net/en/get-involved/guides/monotone" class="external" target="_blank">I2P monotone</a> database, or on <a href="https://github.com/i2p/i2p.plugins.zzzot" class="external" target="_blank">github</a>. Note that the I2P source code must be available (and compiled) in <code>../i2p.i2p</code> in order to build ZzzOT.</p>
|
||||
<p>ZzzOT is a simple in-memory BitTorrent open <a href="https://en.wikipedia.org/wiki/BitTorrent_tracker" class="external" target="_blank">tracker</a>, wrapped into an I2P plugin. The software depends on several I2P libraries, in addition to <code>i2psnark.jar</code> from the I2P installation for <a href="https://en.wikipedia.org/wiki/Bencode" class="external" target="_blank">bencoding</a>. Please report bugs on <a href="http://i2pforum.i2p/" target="_blank">i2pforum.i2p</a>. New releases will be announced on <a href="http://zzz.i2p/forums/16" target="_blank">the plugin forum</a> on zzz.i2p.</p>
|
||||
<p>Source code is available (under the <a href="https://www.apache.org/licenses/LICENSE-2.0.html" class="external" target="_blank">Apache 2.0 license</a>) at <a href="http://git.idk.i2p/I2P_Developers/i2p.plugins.zzzot" target="_blank">our Gitea site</a> and on <a href="https://github.com/i2p/i2p.plugins.zzzot" class="external" target="_blank">github</a>. Note that the I2P source code must be available (and compiled) in <code>../i2p.i2p</code> in order to build ZzzOT.</p>
|
||||
<h3>Configuration & Customization</h3>
|
||||
<hr class="heading">
|
||||
<ul id="config">
|
||||
@ -366,8 +366,10 @@
|
||||
<li>To change the tunnel settings, edit: <code>$PLUGIN/i2ptunnel.config</code> (the tunnel will not appear in the <a href="http://127.0.0.1:7657/i2ptunnelmgr" target="_blank">Tunnel Manager</a>), then stop and restart the plugin from the <a href="http://127.0.0.1:7657/configplugins" target="_blank">Plugin Manager</a>.</li>
|
||||
<li>The Jetty webserver is running on port <code>7662</code>. If you must change it, edit: <code>jetty.xml</code>, <code>i2ptunnel.config</code>, and <code>plugins.config</code> in: <code>$PLUGIN/</code>, then stop and restart the plugin.</li>
|
||||
<li>The configuration file for ZzzOT is located at: <code>$PLUGIN/zzzot.config</code>
|
||||
<li>The only configuration for ZzzOT itself is the <i>interval</i>, which tells clients how long to wait between announces. The default is 1620 seconds (27 minutes).</li>
|
||||
<li>The easiest way to reduce the load on your tracker is to increase the interval. To do so, edit the configuration file, then stop and restart the plugin.</li>
|
||||
<li>The most important configuration for ZzzOT is the <code>interval</code>, which tells clients how long to wait between announces. The default is 1620 seconds (27 minutes).</li>
|
||||
<li>The easiest way to reduce the load on your tracker is to increase the <code>interval</code>. To do so, edit the configuration file, then stop and restart the plugin.</li>
|
||||
<li>UDP may also be enabled or disabled, and the connection lifetime changed, in the configuration file.</li>
|
||||
<li>All conguration changes require the plugin to be stoped and restarted.</li>
|
||||
<li>Live stats are available on the <a href="http://127.0.0.1:7662/tracker" target="_blank">tracker page</a> (this link is also on your router console sidebar when ZzzOT is running).</li>
|
||||
<li>To change the display name for the site (for the logo and page titles), add the line: <code>sitename=<i>mytracker</i></code> to the configuration file (substituting <i>mytracker</i> with your desired name) and then stop and restart the plugin.</li>
|
||||
<li>To change the footer text displayed on the tracker stats page, add the line: <code>footertext=<i>alternative text</i></code> to the configuration file, and then stop and restart the plugin.</li>
|
||||
@ -404,7 +406,11 @@
|
||||
<li><a href="http://$B32/tracker/announce.jsp">http://$B32/tracker/announce.jsp</a></li>
|
||||
<li><a href="http://$B32/tracker/announce.php">http://$B32/tracker/announce.php</a></li>
|
||||
</ul>
|
||||
<p>Supported scrape URLs:</p>
|
||||
<p>UDP announce URL, if enabled (enabled by default):</p>
|
||||
<ul class="urls">
|
||||
<li><a href="udp://$B32:6969/">udp://$B32:6969/</a></li>
|
||||
</ul>
|
||||
<p>Supported scrape URLs (note that full scrapes are disabled by default):</p>
|
||||
<ul class="urls">
|
||||
<li><a href="http://$B32/scrape">http://$B32/scrape</a></li>
|
||||
<li><a href="http://$B32/scrape.jsp">http://$B32/scrape.jsp</a></li>
|
||||
|
@ -16,7 +16,7 @@
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="panel">
|
||||
<a href="/tracker" title="View OpenTracker stats"><span id="sitename">zzzot</span></a>
|
||||
<a href="/tracker/" title="View OpenTracker stats"><span id="sitename">zzzot</span></a>
|
||||
<span id="footer" class="b32">$B32</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
|
||||
|
||||
<!-- ========================================================================= -->
|
||||
<!-- This file configures the Jetty server. -->
|
||||
@ -85,7 +85,7 @@
|
||||
<Call name="addConnector">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.server.ServerConnector">
|
||||
<Arg><Ref id="Server" /></Arg>
|
||||
<Arg><Ref refid="Server" /></Arg>
|
||||
<Arg type="int">1</Arg> <!-- number of acceptors -->
|
||||
<Arg type="int">0</Arg> <!-- default number of selectors -->
|
||||
<Arg>
|
||||
@ -189,6 +189,13 @@
|
||||
<Set name="replacement">/tracker/scrape.jsp</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<!-- BiglyBT -->
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/a/scrape</Set>
|
||||
<Set name="replacement">/tracker/scrape.jsp</Set>
|
||||
</New>
|
||||
</Item>
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
|
||||
<Set name="pattern">/Seedless</Set>
|
||||
@ -250,7 +257,7 @@
|
||||
<Arg>
|
||||
<New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
|
||||
<Set name="contexts">
|
||||
<Ref id="Contexts" />
|
||||
<Ref refid="Contexts" />
|
||||
</Set>
|
||||
<Call name="setContextAttribute">
|
||||
<Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
|
||||
@ -271,7 +278,7 @@
|
||||
<!-- in the $JETTY_HOME/contexts directory -->
|
||||
<!-- -->
|
||||
<!-- =========================================================== -->
|
||||
<Ref id="DeploymentManager">
|
||||
<Ref refid="DeploymentManager">
|
||||
<Call name="addAppProvider">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
|
||||
@ -295,7 +302,7 @@
|
||||
<!-- Normally only one type of deployer need be used. -->
|
||||
<!-- -->
|
||||
<!-- =========================================================== -->
|
||||
<Ref id="DeploymentManager">
|
||||
<Ref refid="DeploymentManager">
|
||||
<Call id="webappprovider" name="addAppProvider">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
|
||||
@ -336,7 +343,7 @@
|
||||
<!-- contexts configuration (see $(jetty.home)/contexts/test.xml -->
|
||||
<!-- for an example). -->
|
||||
<!-- =========================================================== -->
|
||||
<Ref id="RequestLog">
|
||||
<Ref refid="RequestLog">
|
||||
<Set name="requestLog">
|
||||
<New id="RequestLogImpl" class="net.i2p.jetty.I2PRequestLog">
|
||||
<Set name="filename">$PLUGIN/eepsite/logs/yyyy_mm_dd.request.log</Set>
|
||||
|
@ -7,7 +7,7 @@ tunnel.0.option.crypto.tagsToSend=10
|
||||
tunnel.0.option.i2cp.destination.sigType=7
|
||||
tunnel.0.option.i2cp.enableAccessList=false
|
||||
tunnel.0.option.i2cp.encryptLeaseSet=false
|
||||
tunnel.0.option.i2cp.leaseSetEncType=4,0
|
||||
tunnel.0.option.i2cp.leaseSetEncType=4
|
||||
tunnel.0.option.i2cp.reduceIdleTime=1200000
|
||||
tunnel.0.option.i2cp.reduceOnIdle=true
|
||||
tunnel.0.option.i2cp.reduceQuantity=1
|
||||
|
@ -7,6 +7,17 @@
|
||||
# zzz 2010-02
|
||||
# zzz 2014-08 added support for su3 files
|
||||
#
|
||||
|
||||
if [ -z "$I2P" -a -d "$PWD/../i2p.i2p/pkg-temp" ]; then
|
||||
export I2P=../i2p.i2p/pkg-temp
|
||||
fi
|
||||
|
||||
if [ ! -d "$I2P" ]; then
|
||||
echo "Can't locate your I2P installation. Please add a environment variable named I2P with the path to the folder as value"
|
||||
echo "On OSX this solved with running: export I2P=/Applications/i2p if default install directory is used."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PUBKEYDIR=$HOME/.i2p-plugin-keys
|
||||
PUBKEYFILE=$PUBKEYDIR/plugin-public-signing.key
|
||||
PRIVKEYFILE=$PUBKEYDIR/plugin-private-signing.key
|
||||
@ -15,8 +26,6 @@ PUBKEYSTORE=$PUBKEYDIR/plugin-su3-public-signing.crt
|
||||
PRIVKEYSTORE=$PUBKEYDIR/plugin-su3-keystore.ks
|
||||
KEYTYPE=RSA_SHA512_4096
|
||||
|
||||
export I2P=../i2p.i2p/pkg-temp
|
||||
|
||||
PLUGINDIR=${1:-plugin}
|
||||
|
||||
PC=plugin.config
|
||||
|
@ -11,4 +11,4 @@ updateURL.su3=http://stats.i2p/i2p/plugins/zzzot-update.su3
|
||||
websiteURL=http://zzz.i2p/forums/16
|
||||
license=Apache 2.0
|
||||
min-jetty-version=9
|
||||
min-i2p-version=0.9.31
|
||||
min-i2p-version=2.8.2
|
||||
|
@ -42,8 +42,8 @@ html, body {
|
||||
border: 1px solid #555;
|
||||
box-shadow: inset 0 0 0 1px #111, inset 0 0 2px 1px #444, 0 0 2px 2px #000;
|
||||
background: #181818;
|
||||
background: repeating-linear-gradient(to right, rgba(255, 255, 255, .05), rgba(0, 0, 0, .08) 2px),
|
||||
repeating-linear-gradient(to bottom, #222, #111 2px);
|
||||
background: repeating-linear-gradient(to right, rgba(255, 255, 255, .05), rgba(0, 0, 0, .08) 2px) center center / 2px 100%,
|
||||
repeating-linear-gradient(to bottom, #222, #111 2px) center center / 100% 2px;
|
||||
background-blend-mode: overlay;
|
||||
will-change: transform;
|
||||
}
|
||||
|
@ -1,3 +1,25 @@
|
||||
#
|
||||
# All changes require plugin restart
|
||||
#
|
||||
# announce interval in seconds
|
||||
# minimum 900 (15 minutes), maximum 21600 (6 hours)
|
||||
interval=1620
|
||||
#
|
||||
# Enable UDP announces
|
||||
# default false
|
||||
udp=false
|
||||
#
|
||||
# UDP connection lifetime in seconds
|
||||
# minimum 60 (1 minute), maximum 21600 (6 hours)
|
||||
lifetime=1200
|
||||
#
|
||||
#
|
||||
# UDP announce port
|
||||
# default 6969
|
||||
port=6969
|
||||
#
|
||||
showfoooter=true
|
||||
#footerText=your html text here
|
||||
#
|
||||
# default false as of 0.19.0
|
||||
#allowFullScrape=false
|
||||
|
@ -25,7 +25,7 @@
|
||||
</target>
|
||||
|
||||
<property name="javac.compilerargs" value="" />
|
||||
<property name="javac.version" value="1.7" />
|
||||
<property name="javac.version" value="1.8" />
|
||||
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
@ -62,6 +62,7 @@
|
||||
<arg value="build/web-fragment.xml" />
|
||||
<arg value="-webapp" />
|
||||
<arg value="jsp/" />
|
||||
<arg value="-die" />
|
||||
</java>
|
||||
|
||||
<javac
|
||||
|
@ -16,40 +16,24 @@ package net.i2p.zzzot;
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
|
||||
/*
|
||||
* A single peer for a single torrent.
|
||||
* Save a couple stats, and implements
|
||||
* a Map so we can BEncode it
|
||||
* So it's like PeerID but in reverse - we make a Map from the
|
||||
* data. PeerID makes the data from a Map.
|
||||
* Save a couple stats. We no longer support non-compact
|
||||
* announces, so this is no longer a Map that can be BEncoded.
|
||||
* See announce.jsp.
|
||||
*/
|
||||
public class Peer extends HashMap<String, Object> {
|
||||
public class Peer {
|
||||
|
||||
private final Hash hash;
|
||||
private long lastSeen;
|
||||
private long bytesLeft;
|
||||
private static final Integer PORT = Integer.valueOf(6881);
|
||||
|
||||
public Peer(byte[] id, Destination address, ConcurrentMap<String, String> destCache) {
|
||||
super(3);
|
||||
if (id.length != 20)
|
||||
throw new IllegalArgumentException("Bad peer ID length: " + id.length);
|
||||
put("peer id", id);
|
||||
put("port", PORT);
|
||||
// cache the 520-byte address strings
|
||||
String dest = address.toBase64() + ".i2p";
|
||||
String oldDest = destCache.putIfAbsent(dest, dest);
|
||||
if (oldDest != null)
|
||||
dest = oldDest;
|
||||
put("ip", dest);
|
||||
public Peer(byte[] id, Destination address) {
|
||||
hash = address.calculateHash();
|
||||
}
|
||||
|
||||
public void setLeft(long l) {
|
||||
@ -65,14 +49,10 @@ public class Peer extends HashMap<String, Object> {
|
||||
return lastSeen;
|
||||
}
|
||||
|
||||
/** convert b64.i2p to a Hash, then to a binary string */
|
||||
/* or should we just store it in the constructor? cache it? */
|
||||
public String getHash() {
|
||||
String ip = (String) get("ip");
|
||||
byte[] b = Base64.decode(ip.substring(0, ip.length() - 4));
|
||||
Hash h = SHA256Generator.getInstance().calculateHash(b);
|
||||
try {
|
||||
return new String(h.getData(), "ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) { return null; }
|
||||
/**
|
||||
* @since 0.20
|
||||
*/
|
||||
public byte[] getHashBytes() {
|
||||
return hash.getData();
|
||||
}
|
||||
}
|
||||
|
@ -1,60 +0,0 @@
|
||||
package net.i2p.zzzot;
|
||||
/*
|
||||
* Copyright 2010 zzz (zzz@mail.i2p)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocketEepGet;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.util.EepGet;
|
||||
|
||||
/**
|
||||
* Announce to seedless
|
||||
* @since 0.6
|
||||
*/
|
||||
public class SeedlessAnnouncer {
|
||||
|
||||
private static final String SPONGE =
|
||||
"VG4Bd~q1RA3BdoF3z5fSR7p0xe1CTVgDMWVGyFchA9Wm2iXUkIR35G45XE31Uc9~IOt-ktNLL2~TYQZ13Vl8udosngDn8RJG1NtVASH4khsbgkkoFLWd6UuvuOjQKBFKjaEPJgxOzh0kxolRPPNHhFuuAGzNLKvz~LI2MTf0P6nwmRg1lBoRIUpSVocEHY4X306nT2VtY07FixbJcPCU~EeRin24yNoiZop-C3Wi1SGwJJK-NS7mnkNzd8ngDJXDJtR-wLP1vNyyBY6NySgqPiIhENHoVeXd5krlR42HORCxEDb4jhoqlbyJq-PrhTJ5HdH4-~gEq09B~~NIHzy7X02XgmBXhTYRtl6HbLMXs6SI5fq9OFgVp5YZWYUklJjMDI7jOrGrEZGSHhnJK9kT6D3CqVIM0cYEhe4ttmTegbZvC~J6DrRTIAX422qRQJBPsTUnv4iFyuJE-8SodP6ikTjRH21Qx73SxqOvmrOiu7Bsp0lvVDa84aoaYLdiGv87AAAA";
|
||||
|
||||
private static final String ANNOUNCE = "announce " + Base64.encode("seedless,eepsite,torrent");
|
||||
|
||||
public void announce(TunnelController controller) {
|
||||
// get the I2PTunnel from the controller (no method now)
|
||||
|
||||
// get the I2PTunnelTask from I2PTunnel
|
||||
|
||||
// cast to an I2PTunnelServer
|
||||
|
||||
// get the SocketManager from the server (no method now)
|
||||
I2PSocketManager mgr = null;
|
||||
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
String url = "http://" + SPONGE + "/Seedless/seedless";
|
||||
EepGet get = new I2PSocketEepGet(ctx, mgr, 1, -1, 1024, null, new DummyOutputStream(), url);
|
||||
get.addHeader("X-Seedless", ANNOUNCE);
|
||||
get.fetch();
|
||||
}
|
||||
|
||||
private static class DummyOutputStream extends OutputStream {
|
||||
public void write(int b) {}
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ package net.i2p.zzzot;
|
||||
*/
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.CoreVersion;
|
||||
import net.i2p.data.DataHelper;
|
||||
@ -33,15 +34,19 @@ public class Torrents extends ConcurrentHashMap<InfoHash, Peers> {
|
||||
private final SDSCache<InfoHash> _hashCache;
|
||||
private final SDSCache<PID> _pidCache;
|
||||
private final Integer _interval;
|
||||
private final int _udpLifetime;
|
||||
private final AtomicInteger _announces = new AtomicInteger();
|
||||
|
||||
/**
|
||||
* @param interval in seconds
|
||||
* @param udpInterval in seconds
|
||||
*/
|
||||
public Torrents(int interval) {
|
||||
public Torrents(int interval, int udpLifetime) {
|
||||
super();
|
||||
_hashCache = new SDSCache<InfoHash>(InfoHash.class, InfoHash.LENGTH, CACHE_SIZE);
|
||||
_pidCache = new SDSCache<PID>(PID.class, PID.LENGTH, CACHE_SIZE);
|
||||
_interval = Integer.valueOf(interval);
|
||||
_udpLifetime = udpLifetime;
|
||||
}
|
||||
|
||||
public int countPeers() {
|
||||
@ -60,6 +65,14 @@ public class Torrents extends ConcurrentHashMap<InfoHash, Peers> {
|
||||
return _interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return in seconds
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public int getUDPLifetime() {
|
||||
return _udpLifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull from cache or return new
|
||||
*
|
||||
@ -86,6 +99,28 @@ public class Torrents extends ConcurrentHashMap<InfoHash, Peers> {
|
||||
return _pidCache.get(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called for every announce except for event = STOPPED.
|
||||
* Hook it here to keep an announce counter.
|
||||
*
|
||||
* @since 0.20.0
|
||||
*/
|
||||
@Override
|
||||
public Peers putIfAbsent(InfoHash ih, Peers p) {
|
||||
_announces.incrementAndGet();
|
||||
return super.putIfAbsent(ih, p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of announces since the last call.
|
||||
* Resets the counter to zero.
|
||||
*
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public int getAnnounces() {
|
||||
return _announces.getAndSet(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.12.0
|
||||
*/
|
||||
@ -93,6 +128,7 @@ public class Torrents extends ConcurrentHashMap<InfoHash, Peers> {
|
||||
public void clear() {
|
||||
super.clear();
|
||||
clearCaches();
|
||||
_announces.set(0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
436
src/java/net/i2p/zzzot/UDPHandler.java
Normal file
436
src/java/net/i2p/zzzot/UDPHandler.java
Normal file
@ -0,0 +1,436 @@
|
||||
package net.i2p.zzzot;
|
||||
/*
|
||||
* Copyright 2022 zzz (zzz@mail.i2p)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionMuxedListener;
|
||||
import net.i2p.client.datagram.Datagram2;
|
||||
import net.i2p.client.datagram.Datagram3;
|
||||
import net.i2p.crypto.SipHashInline;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.LHMCache;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
/**
|
||||
* Hook into the session and handle UDP announces
|
||||
* Ref: Proposal 160, BEP 15
|
||||
*
|
||||
* @since 0.19.0
|
||||
*/
|
||||
public class UDPHandler implements I2PSessionMuxedListener {
|
||||
|
||||
private final I2PAppContext _context;
|
||||
private final Log _log;
|
||||
private final I2PTunnel _tunnel;
|
||||
private final ZzzOT _zzzot;
|
||||
private final long sipk0, sipk1;
|
||||
private final Map<Hash, Destination> _destCache;
|
||||
private volatile boolean _running;
|
||||
|
||||
// The listen port.
|
||||
public final int PORT;
|
||||
private static final long MAGIC = 0x41727101980L;
|
||||
private static final int ACTION_CONNECT = 0;
|
||||
private static final int ACTION_ANNOUNCE = 1;
|
||||
private static final int ACTION_SCRAPE = 2;
|
||||
private static final int ACTION_ERROR = 3;
|
||||
private static final int MAX_RESPONSES = 25;
|
||||
private static final int EVENT_NONE = 0;
|
||||
private static final int EVENT_COMPLETED = 1;
|
||||
private static final int EVENT_STARTED = 2;
|
||||
private static final int EVENT_STOPPED = 3;
|
||||
// keep it short, we should have the leaseset
|
||||
private final long LOOKUP_TIMEOUT = 1000;
|
||||
private final long CLEAN_TIME;
|
||||
private static final byte[] INVALID = DataHelper.getUTF8("Invalid connection ID");
|
||||
private static final byte[] PROTOCOL = DataHelper.getUTF8("Bad protocol");
|
||||
private static final byte[] SCRAPE = DataHelper.getUTF8("Scrape unsupported");
|
||||
|
||||
public UDPHandler(I2PAppContext ctx, I2PTunnel tunnel, ZzzOT zzzot, int port) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(UDPHandler.class);
|
||||
_tunnel = tunnel;
|
||||
_zzzot = zzzot;
|
||||
CLEAN_TIME = (zzzot.getTorrents().getUDPLifetime() + 60) * 1000;
|
||||
PORT = port;
|
||||
sipk0 = ctx.random().nextLong();
|
||||
sipk1 = ctx.random().nextLong();
|
||||
// the highest-traffic zzzot is running about 3000 announces/minute,
|
||||
// give us enough to respond to the first announce after the connection
|
||||
_destCache = new LHMCache<Hash, Destination>(1024);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
_running = true;
|
||||
(new I2PAppThread(new Waiter(), "ZzzOT UDP startup", true)).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public void stop() {
|
||||
_running = false;
|
||||
}
|
||||
|
||||
private class Waiter implements Runnable {
|
||||
public void run() {
|
||||
while (_running) {
|
||||
// requires I2P 0.9.53 (1.7.0)
|
||||
List<I2PSession> sessions = _tunnel.getSessions();
|
||||
if (sessions.isEmpty()) {
|
||||
try { Thread.sleep(1000); } catch (InterruptedException ie) { break; }
|
||||
continue;
|
||||
}
|
||||
I2PSession session = sessions.get(0);
|
||||
session.addMuxedSessionListener(UDPHandler.this, I2PSession.PROTO_DATAGRAM2, PORT);
|
||||
session.addMuxedSessionListener(UDPHandler.this, I2PSession.PROTO_DATAGRAM3, PORT);
|
||||
if (_log.shouldInfo())
|
||||
_log.info("got session");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// begin listener methods ///
|
||||
|
||||
public void messageAvailable(I2PSession sess, int id, long size) {
|
||||
throw new IllegalStateException("muxed");
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public void messageAvailable(I2PSession session, int id, long size, int proto, int fromPort, int toPort) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Got " + size + " bytes, proto: " + proto + " from port: " + fromPort + " to port: " + toPort);
|
||||
try {
|
||||
// receive message
|
||||
byte[] msg = session.receiveMessage(id);
|
||||
if (proto == I2PSession.PROTO_DATAGRAM2) {
|
||||
// load datagram into it
|
||||
Datagram2 dg = Datagram2.load(_context, session, msg);
|
||||
handle(session, dg.getSender(), null, fromPort, dg.getPayload());
|
||||
} else if (proto == I2PSession.PROTO_DATAGRAM3) {
|
||||
Datagram3 dg = Datagram3.load(_context, session, msg);
|
||||
handle(session, null, dg.getSender(), fromPort, dg.getPayload());
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping message with unknown protocol " + proto);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("error receiving datagram", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession arg0, int arg1) {}
|
||||
|
||||
public void disconnected(I2PSession arg0) {
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession arg0, String arg1, Throwable arg2) {
|
||||
_log.error(arg1, arg2);
|
||||
}
|
||||
|
||||
/// end listener methods ///
|
||||
|
||||
/**
|
||||
* One of from or fromHash non-null
|
||||
* @param from non-null for connect request
|
||||
* @param fromHash non-null for announce request
|
||||
*/
|
||||
private void handle(I2PSession session, Destination from, Hash fromHash, int fromPort, byte[] data) {
|
||||
int sz = data.length;
|
||||
if (sz < 16) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping short msg length " + sz);
|
||||
return;
|
||||
}
|
||||
long connID = DataHelper.fromLong8(data, 0);
|
||||
int action = (int) DataHelper.fromLong(data, 8, 4);
|
||||
if (action == ACTION_CONNECT) {
|
||||
if (connID != MAGIC) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping bad connect magic " + connID);
|
||||
return;
|
||||
}
|
||||
if (from == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping dg3 connect");
|
||||
int transID = (int) DataHelper.fromLong(data, 12, 4);
|
||||
sendError(session, fromHash, fromPort, transID, PROTOCOL);
|
||||
return;
|
||||
}
|
||||
handleConnect(session, from, fromPort, data);
|
||||
} else if (action == ACTION_ANNOUNCE) {
|
||||
if (fromHash == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping dg2 announce");
|
||||
int transID = (int) DataHelper.fromLong(data, 12, 4);
|
||||
sendError(session, from, fromPort, transID, PROTOCOL);
|
||||
return;
|
||||
}
|
||||
handleAnnounce(session, connID, fromHash, fromPort, data);
|
||||
} else if (action == ACTION_SCRAPE) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("got unsupported scrape");
|
||||
int transID = (int) DataHelper.fromLong(data, 12, 4);
|
||||
if (from != null)
|
||||
sendError(session, from, fromPort, transID, SCRAPE);
|
||||
else
|
||||
sendError(session, fromHash, fromPort, transID, SCRAPE);
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping bad action " + action);
|
||||
// TODO send error?
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from non-null
|
||||
*/
|
||||
private void handleConnect(I2PSession session, Destination from, int fromPort, byte[] data) {
|
||||
int transID = (int) DataHelper.fromLong(data, 12, 4);
|
||||
long connID = generateCID(from.calculateHash());
|
||||
byte[] resp = new byte[18];
|
||||
DataHelper.toLong(resp, 4, 4, transID);
|
||||
DataHelper.toLong8(resp, 8, connID);
|
||||
// Addition to BEP 15
|
||||
DataHelper.toLong(resp, 16, 2, _zzzot.getTorrents().getUDPLifetime());
|
||||
try {
|
||||
session.sendMessage(from, resp, I2PSession.PROTO_DATAGRAM_RAW, PORT, fromPort);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("sent connect reply with conn ID " + connID + " to " + from.toBase32());
|
||||
synchronized(_destCache) {
|
||||
_destCache.put(from.calculateHash(), from);
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("error sending connect reply", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from may be null
|
||||
*/
|
||||
private void handleAnnounce(I2PSession session, long connID, Hash fromHash, int fromPort, byte[] data) {
|
||||
int sz = data.length;
|
||||
if (sz < 96) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("dropping short announce length " + sz);
|
||||
return;
|
||||
}
|
||||
int transID = (int) DataHelper.fromLong(data, 12, 4);
|
||||
boolean ok = validateCID(fromHash, connID);
|
||||
if (!ok) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("conn ID invalid: " + connID);
|
||||
sendError(session, fromHash, fromPort, transID, INVALID);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO use a waiter
|
||||
Destination from = lookup(session, fromHash);
|
||||
if (from == null)
|
||||
return;
|
||||
|
||||
// parse packet
|
||||
byte[] bih = new byte[InfoHash.LENGTH];
|
||||
System.arraycopy(data, 16, bih, 0, InfoHash.LENGTH);
|
||||
InfoHash ih = new InfoHash(bih);
|
||||
byte[] bpid = new byte[PID.LENGTH];
|
||||
System.arraycopy(data, 36, bpid, 0, PID.LENGTH);
|
||||
PID pid = new PID(bpid);
|
||||
// ignored
|
||||
//long dl = DataHelper.fromLong8(data, 56);
|
||||
//long ul = DataHelper.fromLong8(data, 72);
|
||||
int event = (int) DataHelper.fromLong(data, 80, 4);
|
||||
long left = event == EVENT_COMPLETED ? 0 : DataHelper.fromLong8(data, 64);
|
||||
// ignored
|
||||
//long ip = DataHelper.fromLong(data, 84, 4);
|
||||
//long key = DataHelper.fromLong(data, 88, 4);
|
||||
long want = DataHelper.fromLong(data, 92, 4);
|
||||
if (want > MAX_RESPONSES)
|
||||
want = MAX_RESPONSES;
|
||||
// ignored
|
||||
//int port = (int) DataHelper.fromLong(data, 96, 2);
|
||||
|
||||
Torrents torrents = _zzzot.getTorrents();
|
||||
Peers peers = torrents.get(ih);
|
||||
if (peers == null && event != EVENT_STOPPED) {
|
||||
peers = new Peers();
|
||||
Peers p2 = torrents.putIfAbsent(ih, peers);
|
||||
if (p2 != null)
|
||||
peers = p2;
|
||||
}
|
||||
int size;
|
||||
int seeds;
|
||||
List<Peer> peerlist;
|
||||
if (event == EVENT_STOPPED) {
|
||||
if (peers != null)
|
||||
peers.remove(pid);
|
||||
peerlist = null;
|
||||
size = 0;
|
||||
seeds = 0;
|
||||
} else {
|
||||
Peer p = peers.get(pid);
|
||||
if (p == null) {
|
||||
p = new Peer(pid.getData(), from);
|
||||
Peer p2 = peers.putIfAbsent(pid, p);
|
||||
if (p2 != null)
|
||||
p = p2;
|
||||
}
|
||||
p.setLeft(left);
|
||||
|
||||
size = peers.size();
|
||||
seeds = peers.countSeeds();
|
||||
if (want <= 0 || event == EVENT_STOPPED) {
|
||||
peerlist = null;
|
||||
} else {
|
||||
peerlist = new ArrayList<Peer>(peers.values());
|
||||
peerlist.remove(p); // them
|
||||
if (want < size - 1) {
|
||||
if (size > 150) {
|
||||
// If size is huge, use random iterator for efficiency
|
||||
List<Peer> rv = new ArrayList<Peer>(size);
|
||||
for (RandomIterator<Peer> iter = new RandomIterator<Peer>(peerlist); iter.hasNext(); ) {
|
||||
rv.add(iter.next());
|
||||
}
|
||||
peerlist = rv;
|
||||
} else {
|
||||
Collections.shuffle(peerlist, _context.random());
|
||||
peerlist = peerlist.subList(0, (int) want);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int count = peerlist != null ? peerlist.size() : 0;
|
||||
byte[] resp = new byte[22 + (32 * count)];
|
||||
resp[3] = (byte) ACTION_ANNOUNCE;
|
||||
DataHelper.toLong(resp, 4, 4, transID);
|
||||
DataHelper.toLong(resp, 8, 4, torrents.getInterval());
|
||||
DataHelper.toLong(resp, 12, 4, size - seeds);
|
||||
DataHelper.toLong(resp, 16, 4, seeds);
|
||||
DataHelper.toLong(resp, 20, 2, count);
|
||||
if (peerlist != null) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
System.arraycopy(peerlist.get(i).getHashBytes(), 0, resp, 22 + (i * 32), 32);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
session.sendMessage(from, resp, I2PSession.PROTO_DATAGRAM_RAW, PORT, fromPort);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("sent announce reply to " + from);
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("error sending announce reply", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from non-null
|
||||
* @param msg non-null
|
||||
*/
|
||||
private void sendError(I2PSession session, Hash toHash, int toPort, long transID, byte[] msg) {
|
||||
// TODO use a waiter
|
||||
Destination to = lookup(session, toHash);
|
||||
if (to == null)
|
||||
return;
|
||||
sendError(session, to, toPort, transID, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from non-null
|
||||
* @param msg non-null
|
||||
*/
|
||||
private void sendError(I2PSession session, Destination to, int toPort, long transID, byte[] msg) {
|
||||
byte[] resp = new byte[8 + msg.length];
|
||||
DataHelper.toLong(resp, 0, 4, ACTION_ERROR);
|
||||
DataHelper.toLong(resp, 4, 4, transID);
|
||||
System.arraycopy(msg, 0, resp, 8, msg.length);
|
||||
try {
|
||||
session.sendMessage(to, resp, I2PSession.PROTO_DATAGRAM_RAW, PORT, toPort);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("sent error to " + to.toBase32());
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("error sending connect reply", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking.
|
||||
* @return null on failure
|
||||
*/
|
||||
private Destination lookup(I2PSession session, Hash hash) {
|
||||
Destination rv;
|
||||
synchronized(_destCache) {
|
||||
rv = _destCache.get(hash);
|
||||
}
|
||||
if (rv != null)
|
||||
return rv;
|
||||
// TODO use a waiter
|
||||
try {
|
||||
rv = session.lookupDest(hash, LOOKUP_TIMEOUT);
|
||||
} catch (I2PSessionException ise) {}
|
||||
if (rv == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("lookup failed for response to " + hash.toBase32());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private long generateCID(Hash hash) {
|
||||
byte[] buf = new byte[40];
|
||||
System.arraycopy(hash.getData(), 0, buf, 0, 32);
|
||||
long time = _context.clock().now() / CLEAN_TIME;
|
||||
DataHelper.toLong8(buf, 32, time);
|
||||
return SipHashInline.hash24(sipk0, sipk1, buf);
|
||||
}
|
||||
|
||||
private boolean validateCID(Hash hash, long cid) {
|
||||
byte[] buf = new byte[40];
|
||||
System.arraycopy(hash.getData(), 0, buf, 0, 32);
|
||||
// current epoch
|
||||
long time = _context.clock().now() / CLEAN_TIME;
|
||||
DataHelper.toLong8(buf, 32, time);
|
||||
long c = SipHashInline.hash24(sipk0, sipk1, buf);
|
||||
if (cid == c)
|
||||
return true;
|
||||
// previous epoch
|
||||
time--;
|
||||
DataHelper.toLong8(buf, 32, time);
|
||||
c = SipHashInline.hash24(sipk0, sipk1, buf);
|
||||
return cid == c;
|
||||
}
|
||||
}
|
@ -29,17 +29,21 @@ import net.i2p.util.SimpleTimer2;
|
||||
*/
|
||||
class ZzzOT {
|
||||
|
||||
private final I2PAppContext _context;
|
||||
private final Torrents _torrents;
|
||||
private final Cleaner _cleaner;
|
||||
private final ConcurrentHashMap<String, String> _destCache = new ConcurrentHashMap<String, String>();
|
||||
private final long EXPIRE_TIME;
|
||||
|
||||
private static final String PROP_INTERVAL = "interval";
|
||||
private static final String PROP_UDP_LIFETIME = "lifetime";
|
||||
private static final long CLEAN_TIME = 4*60*1000;
|
||||
private static final long DEST_CACHE_CLEAN_TIME = 3*60*60*1000;
|
||||
private static final int DEFAULT_INTERVAL = 27*60;
|
||||
private static final int DEFAULT_UDP_LIFETIME = 20*60;
|
||||
private static final int MIN_INTERVAL = 15*60;
|
||||
private static final int MAX_INTERVAL = 6*60*60;
|
||||
private static final int MIN_UDP_LIFETIME = 60;
|
||||
private static final int MAX_UDP_LIFETIME = 6*60*60;
|
||||
|
||||
ZzzOT(I2PAppContext ctx, Properties p) {
|
||||
String intv = p.getProperty(PROP_INTERVAL);
|
||||
@ -53,28 +57,41 @@ class ZzzOT {
|
||||
interval = MAX_INTERVAL;
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
_torrents = new Torrents(interval);
|
||||
intv = p.getProperty(PROP_UDP_LIFETIME);
|
||||
int lifetime = DEFAULT_UDP_LIFETIME;
|
||||
if (intv != null) {
|
||||
try {
|
||||
lifetime = Integer.parseInt(intv);
|
||||
if (lifetime < MIN_UDP_LIFETIME)
|
||||
interval = MIN_UDP_LIFETIME;
|
||||
else if (interval > MAX_UDP_LIFETIME)
|
||||
interval = MAX_UDP_LIFETIME;
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
_torrents = new Torrents(interval, lifetime);
|
||||
EXPIRE_TIME = 1000 * (interval + interval / 2);
|
||||
_cleaner = new Cleaner(ctx);
|
||||
_context = ctx;
|
||||
}
|
||||
|
||||
Torrents getTorrents() {
|
||||
return _torrents;
|
||||
}
|
||||
|
||||
/** @since 0.9.14 */
|
||||
ConcurrentHashMap<String, String> getDestCache() {
|
||||
return _destCache;
|
||||
}
|
||||
|
||||
void start() {
|
||||
_cleaner.forceReschedule(CLEAN_TIME);
|
||||
long[] r = new long[] { 5*60*1000 };
|
||||
_context.statManager().createRequiredRateStat("plugin.zzzot.announces", "Announces per minute", "Plugins", r);
|
||||
_context.statManager().createRequiredRateStat("plugin.zzzot.peers", "Number of peers", "Plugins", r);
|
||||
_context.statManager().createRequiredRateStat("plugin.zzzot.torrents", "Number of torrents", "Plugins", r);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
_cleaner.cancel();
|
||||
_torrents.clear();
|
||||
_destCache.clear();
|
||||
_context.statManager().removeRateStat("plugin.zzzot.announces");
|
||||
_context.statManager().removeRateStat("plugin.zzzot.peers");
|
||||
_context.statManager().removeRateStat("plugin.zzzot.torrents");
|
||||
}
|
||||
|
||||
private class Cleaner extends SimpleTimer2.TimedEvent {
|
||||
@ -88,6 +105,7 @@ class ZzzOT {
|
||||
|
||||
public void timeReached() {
|
||||
long now = System.currentTimeMillis();
|
||||
int peers = 0;
|
||||
for (Iterator<Peers> iter = _torrents.values().iterator(); iter.hasNext(); ) {
|
||||
Peers p = iter.next();
|
||||
int recent = 0;
|
||||
@ -100,10 +118,12 @@ class ZzzOT {
|
||||
}
|
||||
if (recent <= 0)
|
||||
iter.remove();
|
||||
else
|
||||
peers += recent;
|
||||
}
|
||||
if (_runCount.incrementAndGet() % (DEST_CACHE_CLEAN_TIME / CLEAN_TIME) == 0) {
|
||||
_destCache.clear();
|
||||
}
|
||||
_context.statManager().addRateData("plugin.zzzot.announces", _torrents.getAnnounces() / (CLEAN_TIME / (60*1000L)));
|
||||
_context.statManager().addRateData("plugin.zzzot.peers", peers);
|
||||
_context.statManager().addRateData("plugin.zzzot.torrents", _torrents.size());
|
||||
schedule(CLEAN_TIME);
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,8 @@ import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKeyFile;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
@ -67,19 +69,30 @@ public class ZzzOTController implements ClientApp {
|
||||
private static volatile ZzzOTController _controller;
|
||||
// you wouldn't run two instances in the same JVM, would you?
|
||||
private static String _sitename;
|
||||
private static String _showfooter;
|
||||
private static boolean _showfooter;
|
||||
private static String _footertext;
|
||||
private static boolean _fullScrape;
|
||||
private final boolean _enableUDP;
|
||||
private final int _udpPort;
|
||||
private UDPHandler _udp;
|
||||
private String _b32;
|
||||
|
||||
private ClientAppState _state = UNINITIALIZED;
|
||||
|
||||
private static final String NAME = "ZzzOT";
|
||||
private static final String DEFAULT_SITENAME = "ZZZOT";
|
||||
private static final String PROP_SITENAME = "sitename";
|
||||
private static final String VERSION = "0.18.0";
|
||||
private static final String VERSION = "0.20.0-beta";
|
||||
private static final String DEFAULT_SHOWFOOTER = "true";
|
||||
private static final String PROP_SHOWFOOTER = "showfooter";
|
||||
private static final String DEFAULT_FOOTERTEXT = "Running <a href=\"https://github.com/i2p/i2p.plugins.zzzot\" target=\"_blank\">ZZZOT</a> " + VERSION;
|
||||
private static final String DEFAULT_FOOTERTEXT = "Running <a href=\"http://git.idk.i2p/i2p-hackers/i2p.plugins.zzzot\" target=\"_blank\">ZZZOT</a> " + VERSION;
|
||||
private static final String PROP_FOOTERTEXT = "footertext";
|
||||
private static final String PROP_FULLSCRAPE = "allowFullScrape";
|
||||
private static final String DEFAULT_FULLSCRAPE = "false";
|
||||
private static final String PROP_UDP = "udp";
|
||||
private static final String DEFAULT_UDP = "false";
|
||||
private static final String PROP_UDP_PORT = "udp";
|
||||
private static final int DEFAULT_UDP_PORT = 6969;
|
||||
private static final String CONFIG_FILE = "zzzot.config";
|
||||
private static final String BACKUP_SUFFIX = ".jetty8";
|
||||
private static final String[] xmlFiles = {
|
||||
@ -108,8 +121,18 @@ public class ZzzOTController implements ClientApp {
|
||||
}
|
||||
_zzzot = new ZzzOT(ctx, props);
|
||||
_sitename = props.getProperty(PROP_SITENAME, DEFAULT_SITENAME);
|
||||
_showfooter = props.getProperty(PROP_SHOWFOOTER, DEFAULT_SHOWFOOTER);
|
||||
_showfooter = Boolean.parseBoolean(props.getProperty(PROP_SHOWFOOTER, DEFAULT_SHOWFOOTER));
|
||||
_footertext = props.getProperty(PROP_FOOTERTEXT, DEFAULT_FOOTERTEXT);
|
||||
_fullScrape = Boolean.parseBoolean(props.getProperty(PROP_FULLSCRAPE, DEFAULT_FULLSCRAPE));
|
||||
_enableUDP = Boolean.parseBoolean(props.getProperty(PROP_UDP, DEFAULT_UDP));
|
||||
int p = DEFAULT_UDP_PORT;
|
||||
String port = props.getProperty(PROP_UDP_PORT);
|
||||
if (port != null) {
|
||||
try {
|
||||
p = Integer.parseInt(port);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
_udpPort = p;
|
||||
_state = INITIALIZED;
|
||||
}
|
||||
|
||||
@ -123,31 +146,92 @@ public class ZzzOTController implements ClientApp {
|
||||
/**
|
||||
* @return null if not running
|
||||
*/
|
||||
public static Torrents getTorrents() {
|
||||
private static ZzzOTController getThis() {
|
||||
ClientAppManager mgr = I2PAppContext.getGlobalContext().clientAppManager();
|
||||
if (mgr == null)
|
||||
return null;
|
||||
ClientApp z = mgr.getRegisteredApp(NAME);
|
||||
if (z == null)
|
||||
return null;
|
||||
ZzzOTController ctrlr = (ZzzOTController) z;
|
||||
return ctrlr._zzzot.getTorrents();
|
||||
return (ZzzOTController) z;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return null if not running
|
||||
* @since 0.9.14
|
||||
*/
|
||||
public static ConcurrentMap<String, String> getDestCache() {
|
||||
ClientAppManager mgr = I2PAppContext.getGlobalContext().clientAppManager();
|
||||
if (mgr == null)
|
||||
public static Torrents getTorrents() {
|
||||
ZzzOTController ctrlr = getThis();
|
||||
if (ctrlr == null)
|
||||
return null;
|
||||
ClientApp z = mgr.getRegisteredApp(NAME);
|
||||
if (z == null)
|
||||
return ctrlr._zzzot.getTorrents();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return announces per minute, 0 if not running
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public static double getAnnounceRate() {
|
||||
RateStat rs = I2PAppContext.getGlobalContext().statManager().getRate("plugin.zzzot.announces");
|
||||
if (rs == null)
|
||||
return 0;
|
||||
Rate r = rs.getRate(5*60*1000);
|
||||
if (r == null)
|
||||
return 0;
|
||||
return r.getAvgOrLifetimeAvg();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false if not running
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public static boolean isUDPEnabled() {
|
||||
ZzzOTController ctrlr = getThis();
|
||||
if (ctrlr == null)
|
||||
return false;
|
||||
return ctrlr.getUDPEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false if not running
|
||||
* @since 0.20.0
|
||||
*/
|
||||
private boolean getUDPEnabled() {
|
||||
return _enableUDP;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 0 if not running
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public static int udpPort() {
|
||||
ZzzOTController ctrlr = getThis();
|
||||
if (ctrlr == null)
|
||||
return 0;
|
||||
return ctrlr.getUDPPort();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public int getUDPPort() {
|
||||
return _udpPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if not running
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public static String b32() {
|
||||
ZzzOTController ctrlr = getThis();
|
||||
if (ctrlr == null)
|
||||
return null;
|
||||
ZzzOTController ctrlr = (ZzzOTController) z;
|
||||
return ctrlr._zzzot.getDestCache();
|
||||
return ctrlr.getB32();
|
||||
}
|
||||
/**
|
||||
* @since 0.20.0
|
||||
*/
|
||||
public String getB32() {
|
||||
return _b32;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -182,7 +266,11 @@ public class ZzzOTController implements ClientApp {
|
||||
startJetty(pluginDir, dest);
|
||||
startI2PTunnel(pluginDir, dest);
|
||||
_zzzot.start();
|
||||
// SeedlessAnnouncer.announce(_tunnel);
|
||||
// requires I2P 0.9.66 (2.9.0)
|
||||
if (_enableUDP) {
|
||||
_udp = new UDPHandler(_context, _tunnel.getTunnel(), _zzzot, _udpPort);
|
||||
_udp.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -195,17 +283,20 @@ public class ZzzOTController implements ClientApp {
|
||||
_log.error("Cannot open " + i2ptunnelConfig.getAbsolutePath() + ' ' + ioe);
|
||||
throw new IllegalArgumentException("Cannot open " + i2ptunnelConfig.getAbsolutePath() + ' ' + ioe);
|
||||
}
|
||||
if (i2ptunnelProps.getProperty("tunnel.0.option.i2cp.leaseSetEncType") == null)
|
||||
i2ptunnelProps.setProperty("tunnel.0.option.i2cp.leaseSetEncType", "4,0");
|
||||
String p = i2ptunnelProps.getProperty("tunnel.0.option.i2cp.leaseSetEncType");
|
||||
if (p == null || p.equals("4,0"))
|
||||
i2ptunnelProps.setProperty("tunnel.0.option.i2cp.leaseSetEncType", "4");
|
||||
TunnelController tun = new TunnelController(i2ptunnelProps, "tunnel.0.");
|
||||
if (dest != null) {
|
||||
if (dest == null) {
|
||||
// start in foreground so we can get the destination
|
||||
tun.startTunnel();
|
||||
_b32 = tun.getMyDestHashBase32();
|
||||
List msgs = tun.clearMessages();
|
||||
for (Object s : msgs) {
|
||||
_log.logAlways(Log.INFO, "NOTICE: ZzzOT Tunnel message: " + s);
|
||||
}
|
||||
} else {
|
||||
_b32 = dest.calculateHash().toBase32();
|
||||
tun.startTunnelBackground();
|
||||
}
|
||||
_tunnel = tun;
|
||||
@ -238,6 +329,8 @@ public class ZzzOTController implements ClientApp {
|
||||
private void stop() {
|
||||
stopI2PTunnel();
|
||||
stopJetty();
|
||||
if (_udp != null)
|
||||
_udp.stop();
|
||||
_zzzot.stop();
|
||||
}
|
||||
|
||||
@ -489,7 +582,7 @@ public class ZzzOTController implements ClientApp {
|
||||
}
|
||||
|
||||
/** @since 0.17.0 */
|
||||
public static String shouldShowFooter() {
|
||||
public static boolean shouldShowFooter() {
|
||||
return _showfooter;
|
||||
}
|
||||
|
||||
@ -498,6 +591,11 @@ public class ZzzOTController implements ClientApp {
|
||||
return _footertext;
|
||||
}
|
||||
|
||||
/** @since 0.19.0 */
|
||||
public static boolean allowFullScrape() {
|
||||
return _fullScrape;
|
||||
}
|
||||
|
||||
/** @since 0.12.0 */
|
||||
private synchronized void changeState(ClientAppState state) {
|
||||
_state = state;
|
||||
|
@ -47,4 +47,10 @@
|
||||
<url-pattern>/scrape.php</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- BiglyBT -->
|
||||
<servlet-mapping>
|
||||
<servlet-name>net.i2p.zzzot.scrape_jsp</servlet-name>
|
||||
<url-pattern>/a/scrape</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
</web-app>
|
||||
|
@ -29,6 +29,7 @@
|
||||
final int MAX_RESPONSES = 25;
|
||||
final boolean ALLOW_IP_MISMATCH = false;
|
||||
final boolean ALLOW_COMPACT_RESPONSE = true;
|
||||
final boolean ALLOW_NONCOMPACT_RESPONSE = false;
|
||||
|
||||
// so the chars will turn into bytes correctly
|
||||
request.setCharacterEncoding("ISO-8859-1");
|
||||
@ -68,6 +69,11 @@
|
||||
response.setStatus(403);
|
||||
}
|
||||
|
||||
if (!compact && !ALLOW_NONCOMPACT_RESPONSE && !fail) {
|
||||
fail = true;
|
||||
msg = "non-compact responses unsupported";
|
||||
}
|
||||
|
||||
if (info_hash == null && !fail) {
|
||||
fail = true;
|
||||
msg = "no info hash";
|
||||
@ -198,8 +204,7 @@
|
||||
// fixme same peer id, different dest
|
||||
Peer p = peers.get(pid);
|
||||
if (p == null) {
|
||||
ConcurrentMap<String, String> destCache = ZzzOTController.getDestCache();
|
||||
p = new Peer(pid.getData(), d, destCache);
|
||||
p = new Peer(pid.getData(), d);
|
||||
// don't add if spoofed
|
||||
if (matchIP) {
|
||||
Peer p2 = peers.putIfAbsent(pid, p);
|
||||
@ -236,18 +241,23 @@
|
||||
}
|
||||
}
|
||||
if (compact) {
|
||||
// old experimental way - list of hashes
|
||||
//List<String> peerhashes = new ArrayList(peerlist.size());
|
||||
//for (Peer pe : peerlist) {
|
||||
// peerhashes.add(pe.getHash());
|
||||
//}
|
||||
// new way - one big string
|
||||
// one big string
|
||||
byte[] peerhashes = new byte[32 * peerlist.size()];
|
||||
for (int i = 0; i < peerlist.size(); i++)
|
||||
System.arraycopy(peerlist.get(i).getHash().getBytes("ISO-8859-1"), 0, peerhashes, i * 32, 32);
|
||||
System.arraycopy(peerlist.get(i).getHashBytes(), 0, peerhashes, i * 32, 32);
|
||||
m.put("peers", peerhashes);
|
||||
} else if (ALLOW_NONCOMPACT_RESPONSE) {
|
||||
// This requires the Peer entries to be Maps
|
||||
// so they can be bencoded, but we don't save
|
||||
// the full Destination any more, and Peer does not.
|
||||
// extend HashMap, to greatly reduce memory usage.
|
||||
// We could create a Map here with the b32 as the IP,
|
||||
// but that's nonstandard. So if non-compact is enabled,
|
||||
// don't return any peers.
|
||||
//m.put("peers", peerlist);
|
||||
m.put("peers", java.util.Collections.EMPTY_LIST);
|
||||
} else {
|
||||
m.put("peers", peerlist);
|
||||
// won't get here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,29 @@
|
||||
<p id="totals">
|
||||
<b>Torrents:</b> <%=torrents.size()%><br>
|
||||
<b>Peers:</b> <%=torrents.countPeers()%><br>
|
||||
<b>Announce Rate:</b> <%=String.format(java.util.Locale.US, "%.1f", ZzzOTController.getAnnounceRate())%> / minute<br>
|
||||
<b>Announce Interval:</b> <%=torrents.getInterval() / 60%> minutes<br>
|
||||
<%
|
||||
String host = ZzzOTController.b32();
|
||||
if (host != null) {
|
||||
%><b>Announce URL:</b> <a href="http://<%=host%>/a">http://<%=host%>/a</a><br><%
|
||||
}
|
||||
boolean udp = ZzzOTController.isUDPEnabled();
|
||||
%>
|
||||
<b>UDP Announce Support:</b> <%=udp ? "yes" : "no"%><br>
|
||||
<%
|
||||
if (udp) {
|
||||
%>
|
||||
<b>UDP Connection Lifetime:</b> <%=torrents.getUDPLifetime() / 60%> minutes<br>
|
||||
<%
|
||||
if (host != null) {
|
||||
int port = ZzzOTController.udpPort();
|
||||
%>
|
||||
<b>UDP Announce URL:</b> <a href="udp://<%=host%>:<%=port%>/"</a>udp://<%=host%>:<%=port%>/</a><br>
|
||||
<%
|
||||
}
|
||||
}
|
||||
%>
|
||||
</p>
|
||||
<%
|
||||
} else {
|
||||
@ -29,8 +52,8 @@
|
||||
}
|
||||
%>
|
||||
<%
|
||||
String showfooter = ZzzOTController.shouldShowFooter();
|
||||
if (showfooter == "true") {
|
||||
boolean showfooter = ZzzOTController.shouldShowFooter();
|
||||
if (showfooter) {
|
||||
%>
|
||||
<span id="footer" class="version"><%=ZzzOTController.footerText()%></span>
|
||||
<%
|
||||
|
@ -50,8 +50,12 @@
|
||||
}
|
||||
|
||||
boolean all = info_hash == null;
|
||||
if (all && !ZzzOTController.allowFullScrape()) {
|
||||
fail = true;
|
||||
msg = "unsupported";
|
||||
}
|
||||
|
||||
Torrents torrents = ZzzOTController.getTorrents();
|
||||
Torrents torrents = fail ? null : ZzzOTController.getTorrents();
|
||||
if (torrents == null && !fail) {
|
||||
fail = true;
|
||||
msg = "tracker is down";
|
||||
|
@ -17,86 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
String req = request.getHeader("X-Seedless");
|
||||
// extension for ease of eepget and browser
|
||||
if (req == null)
|
||||
req = request.getParameter("X-Seedless");
|
||||
// we should really put in our own b32
|
||||
String me = request.getHeader("Host");
|
||||
if (me == null)
|
||||
me = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.b32.i2p";
|
||||
// unused, we don't accept announces
|
||||
String him = request.getHeader("X-I2P-DestB32");
|
||||
if (him == null)
|
||||
him = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.b32.i2p";
|
||||
String xff = request.getHeader("X-Forwarded-For");
|
||||
String xfs = request.getHeader("X-Forwarded-Server");
|
||||
|
||||
response.setContentType("text/plain");
|
||||
response.setHeader("X-Seedless", him);
|
||||
|
||||
final int US_MINUTES = 360;
|
||||
final int PEER_MINUTES = 60;
|
||||
|
||||
if (xff != null || xfs != null) {
|
||||
String msg = "Non-I2P access denied";
|
||||
//response.setStatus(403, msg);
|
||||
response.setStatus(403);
|
||||
out.println(msg);
|
||||
} else if (req == null) {
|
||||
// probe
|
||||
out.println("tracker " + US_MINUTES);
|
||||
out.println("eepsite " + US_MINUTES);
|
||||
out.println("seedless " + US_MINUTES);
|
||||
} else if (req.startsWith("announce")) {
|
||||
out.println("thanks");
|
||||
} else if (req.startsWith("locate c2VlZGxlc")) { // locate b64(seedless)
|
||||
// ignore the search string, if any, in the request
|
||||
// us
|
||||
out.println(Base64.encode(me + ' ' + US_MINUTES + " tracker"));
|
||||
out.println(Base64.encode(me + ' ' + US_MINUTES + " seedless"));
|
||||
out.println(Base64.encode(me + ' ' + US_MINUTES + " eepsite"));
|
||||
} else if (req.startsWith("locate ZWVwc2l0Z")) { // locate b64(eepsite)
|
||||
// ignore the search string, if any, in the request
|
||||
// us
|
||||
out.println(Base64.encode(me + ' ' + US_MINUTES + " zzzot"));
|
||||
} else if (req.startsWith("locate dG9ycmVud")) { // locate b64(torrent)
|
||||
// all the peers
|
||||
Torrents torrents = ZzzOTController.getTorrents();
|
||||
if (torrents == null) {
|
||||
//response.setStatus(503, "Down");
|
||||
response.setStatus(503);
|
||||
return;
|
||||
}
|
||||
for (InfoHash ihash : torrents.keySet()) {
|
||||
Peers peers = torrents.get(ihash);
|
||||
if (peers == null)
|
||||
continue;
|
||||
for (Peer p : peers.values()) {
|
||||
// dest to b32
|
||||
String ip = (String) p.get("ip");
|
||||
if (ip.endsWith(".i2p"))
|
||||
ip = ip.substring(0, ip.length() - 4);
|
||||
String b32 = Base32.encode(SHA256Generator.getInstance().calculateHash(Base64.decode(ip)).getData()) + ".b32.i2p ";
|
||||
// service type
|
||||
String role;
|
||||
if (p.isSeed())
|
||||
role = "seed";
|
||||
else
|
||||
role = "leech";
|
||||
// spg wants UTF-8 but all we have is binary data, so hex it
|
||||
String ihs = DataHelper.toHexString(ihash.getData());
|
||||
String ids = DataHelper.toHexString((byte[])p.get("peer id"));
|
||||
out.println(Base64.encode(b32 + PEER_MINUTES + ihs + '\n' +
|
||||
ids + '\n' +
|
||||
role));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// error code
|
||||
//response.setStatus(406, "Bad request");
|
||||
response.setStatus(406);
|
||||
out.println("SC_NOT_ACCEPTABLE");
|
||||
}
|
||||
|
||||
%>
|
Reference in New Issue
Block a user