516 lines
24 KiB
HTML
516 lines
24 KiB
HTML
{% extends "_layout.html" %}
|
|
{% block title %}Application Development{% endblock %}
|
|
{% block content %}
|
|
<h1>Application Development Guide</h1>
|
|
|
|
<h2>Contents</h2>
|
|
<ul>
|
|
<li><a href="#why">Why write I2P-specific code?</a></li>
|
|
<li><a href="#concepts">Important concepts</a></li>
|
|
<li><a href="#options">Development options</a></li>
|
|
<li><a href="#start"><b>Start developing - a simple guide</b></a></li>
|
|
</ul>
|
|
|
|
<h2 id="why">Why write I2P-specific code?</h2>
|
|
|
|
<p>
|
|
There are multiple ways to use applications in I2P.
|
|
Using <a href="/i2ptunnel.html">I2PTunnel</a>,
|
|
you can use regular applications without needing to program explicit I2P support.
|
|
This is very effective for client-server scenario's,
|
|
where you need to connect to a single website.
|
|
You can simply create a tunnel using I2PTunnel to connect to that website, as shown in <a href="#tunnel.serverclient">Figure 1</a>.
|
|
</p>
|
|
<p>
|
|
If your application is distributed, it will require connections to a large amount of peers.
|
|
Using I2PTunnel, you will need to create a new tunnel for each peer you want to contact,
|
|
as shown in <a href="#tunnel.peertopeer">Figure 2</a>.
|
|
This process can of course be automated, but running a lot of I2PTunnel instances creates a large amount of overhead.
|
|
In addition, with many protocols you will need to force everyone to
|
|
use the same set of ports for all peers - e.g. if you want to reliably run DCC
|
|
chat, everyone needs to agree that port 10001 is Alice, port 10002 is Bob, port
|
|
10003 is Charlie, and so on, since the protocol includes TCP/IP specific information
|
|
(host and port).
|
|
</p>
|
|
<p>
|
|
General network applications often send a lot of additional data that could be used to identify users.
|
|
Hostnames, port numbers, time zones, character sets, etc. are often sent without informing the user.
|
|
As such, designing the network protocol specifically with anonymity in mind
|
|
can avoid compromising user identities.
|
|
</p>
|
|
<p>
|
|
There are also efficiency considerations to review when determining how to
|
|
interact on top of I2P. The streaming library and things built on top of it
|
|
operate with handshakes similar to TCP, while the core I2P protocols (I2NP and I2CP)
|
|
are strictly message based (like UDP or in some instances raw IP). The important
|
|
distinction is that with I2P, communication is operating over a long fat network -
|
|
each end to end message will have nontrivial latencies, but may contain payloads
|
|
of up to 32KB. An application that needs a simple request and response can get rid
|
|
of any state and drop the latency incurred by the startup and teardown handshakes
|
|
by using (best effort) datagrams without having to worry about MTU detection or
|
|
fragmentation of messages under 32KB.
|
|
</p>
|
|
<center>
|
|
<div class="box" id="tunnel.serverclient">
|
|
<img src="_static/images/i2ptunnel_serverclient.png" alt="Creating a server-client connection using I2PTunnel only requires creating a single tunnel." title="Creating a server-client connection using I2PTunnel only requires creating a single tunnel." />
|
|
<br /><br />
|
|
Figure 1: Creating a server-client connection using I2PTunnel only requires creating a single tunnel.
|
|
</div>
|
|
</center><br/>
|
|
<center>
|
|
<div class="box" id="tunnel.peertopeer">
|
|
<img src="_static/images/i2ptunnel_peertopeer.png" alt="Setting up connections for a peer-to-peer applications requires a very large amount of tunnels." title="Setting up connections for a peer-to-peer applications requires a very large amount of tunnels." />
|
|
<br /><br />
|
|
Figure 2: Setting up connections for a peer-to-peer applications requires a very large amount of tunnels.
|
|
</div>
|
|
</center><br/>
|
|
<p>
|
|
In summary, a number of reasons to write I2P-specific code:
|
|
<ul>
|
|
<li>
|
|
Creating a large amount of I2PTunnel instances consumes a non-trivial amount of resources,
|
|
which is problematic for distributed applications (a new tunnel is required for each peer).
|
|
</li>
|
|
<li>
|
|
General network protocols often send a lot of additional data that can be used to identify users.
|
|
Programming specifically for I2P allows the creation of a network protocol
|
|
that does not leak such information, keeping users anonymous and secure.
|
|
</li>
|
|
<li>
|
|
Network protocols designed for use on the regular internet can be inefficient
|
|
on I2P, which is a network with a much higher latency.
|
|
</li>
|
|
</ul>
|
|
</p>
|
|
|
|
<p>
|
|
Applications written in Java and accessible/runnable
|
|
using an HTML interface via the standard webapps/app.war
|
|
may be considered for inclusion in the i2p distribution.
|
|
</p>
|
|
|
|
<h2>Important concepts</h2>
|
|
|
|
<p>There are a few changes that require adjusting to when using I2P:</p>
|
|
|
|
<h3>Destination ~= host+port</h3>
|
|
|
|
<p>An application running on I2P sends messages from and receives messages to a
|
|
unique cryptographically secure end point - a "destination". In TCP or UDP
|
|
terms, a destination could (largely) be considered the equivalent of a hostname
|
|
plus port number pair, though there are a few differences. </p>
|
|
|
|
<ul>
|
|
<li>An I2P destination itself is a cryptographic construct - all data sent to one is
|
|
encrypted as if there were universal deployment of IPsec with the (anonymized)
|
|
location of the end point signed as if there were universal deployment of DNSSEC. </li>
|
|
<li>I2P destinations are mobile identifiers - they can be moved from one I2P router
|
|
to another (or with some special software, it can even operate on multiple routers at
|
|
once). This is quite different from the TCP or UDP world where a single end point (port)
|
|
must stay on a single host.</li>
|
|
<li>
|
|
<p>
|
|
I2P destinations are ugly and large - behind the scenes, they contain a 2048bit ElGamal
|
|
public key for encryption, a 1024bit DSA public key for signing, and a variable size
|
|
certificate, which may contain proof of work or blinded data.
|
|
</p>
|
|
<p>
|
|
There are existing ways to refer to these large and ugly destinations by short
|
|
and pretty names (e.g. "irc.duck.i2p"), but at the moment those techniques do not guarantee
|
|
globally uniqueness (since they're stored locally at each person's machine as "hosts.txt")
|
|
and the current mechanism is not especially scalable nor secure (updates to one host file are
|
|
manually managed within Monotone, and as such, anyone with commit rights on the repository can
|
|
change the destinations). There may be some secure, human readable, scalable, and globally
|
|
unique, naming system some day, but applications shouldn't depend upon it being in place,
|
|
since there are those who don't think such a beast is possible.
|
|
<a href="naming.html">Further information on the naming system</a> is available.
|
|
</p>
|
|
</li>
|
|
</ul>
|
|
|
|
<h3>Anonymity and confidentiality</h3>
|
|
|
|
<p>A useful thing to remember is that I2P has transparent end to end encryption
|
|
and authentication for all data passed over the network - if Bob sends to Alice's destination,
|
|
only Alice's destination can receive it, and if Bob is using the datagrams or streaming
|
|
library, Alice knows for certain that Bob's destination is the one who sent the data. </p>
|
|
|
|
<p>Of course, another useful thing to remember is that I2P transparently anonymizes the
|
|
data sent between Alice and Bob, but it does nothing to anonymize the content of what they
|
|
send. For instance, if Alice sends Bob a form with her full name, government IDs, and
|
|
credit card numbers, there is nothing I2P can do. As such, protocols and applications should
|
|
keep in mind what information they are trying to protect and what information they are willing
|
|
to expose.</p>
|
|
|
|
<h3>I2P datagrams can be up to 32KB</h3>
|
|
|
|
<p>Applications that use I2P datagrams (either raw or repliable ones) can essentially be thought
|
|
of in terms of UDP - the datagrams are unordered, best effort, and connectionless - but unlike
|
|
UDP, applications don't need to worry about MTU detection and can simply fire off 32KB datagrams
|
|
(31KB when using the repliable kind). For many applications, 32KB of data is sufficient for an
|
|
entire request or response, allowing them to transparently operate in I2P as a UDP-like
|
|
application without having to write fragmentation, resends, etc.</p>
|
|
|
|
<h2 id="options">Development options</h2>
|
|
|
|
<p>There are several means of sending data over I2P, each with their own pros and cons.
|
|
The streaming lib is the recommended interface, used by the majority of I2P applications.
|
|
</p>
|
|
|
|
<h3>Streaming Lib</h3>
|
|
<p>
|
|
The <a href="streaming.html">full streaming library</a> is now the standard
|
|
interface. It allows programming using TCP-like sockets, as explained in the <a href="#start.streaming">Streaming development guide</a>.
|
|
</p>
|
|
|
|
<h3>SAM, SAM V2, SAM V3</h3>
|
|
|
|
<p><i>SAM is not recommended. SAM V2 is okay, SAM V3 is beta.</i></p>
|
|
<p>SAM is the <a href="sam">Simple Anonymous Messaging</a> protocol, allowing an
|
|
application written in any language to talk to a SAM bridge through a plain TCP socket and have
|
|
that bridge multiplex all of its I2P traffic, transparently coordinating the encryption/decryption
|
|
and event based handling. SAM supports three styles of operation:</p>
|
|
<ul>
|
|
<li>streams, for when Alice and Bob want to send data to each other reliably and in order</li>
|
|
<li>repliable datagrams, for when Alice wants to send Bob a message that Bob can reply to</li>
|
|
<li>raw datagrams, for when Alice wants to squeeze the most bandwidth and performance as possible,
|
|
and Bob doesn't care whether the data's sender is authenticated or not (e.g. the data transferred
|
|
is self authenticating)</li>
|
|
</ul>
|
|
<p>SAM V3<p> aims at the same goal as SAM and SAM V2, but does not require
|
|
multiplexing/demultiplexing. Each I2P stream is handled by its own socket between the application
|
|
and the SAM bride. Besides, datagrams can be sent and received by the application through datagram
|
|
communications with the SAM bridge.
|
|
|
|
</p>
|
|
|
|
<p>
|
|
<a href="samv2.html">SAM V2</a> is a new version used by imule
|
|
that fixes some of the problems in <a href="sam.html">SAM</a>.
|
|
<br />
|
|
<a href="samv3.html">SAM V3</a> is used by imule since version 1.4.0.
|
|
</p>
|
|
|
|
<h3>I2PTunnel</h3>
|
|
<p>The I2PTunnel application allows applications to build specific TCP-like tunnels to peers
|
|
by creating either I2PTunnel 'client' applications (which listen on a specific port and connect
|
|
to a specific I2P destination whenever a socket to that port is opened) or I2PTunnel 'server'
|
|
applications (which listen to a specific I2P destination and whenever it gets a new I2P
|
|
connection it outproxies to a specific TCP host/port). These streams are 8bit clean and are
|
|
authenticated and secured through the same streaming library that SAM uses, but there is a
|
|
nontrivial overhead involved with creating multiple unique I2PTunnel instances, since each have
|
|
their own unique I2P destination and their own set of tunnels, keys, etc.</p>
|
|
|
|
<h3>Ministreaming</h3>
|
|
<p><i>Not recommended</i></p>
|
|
<p>
|
|
It was possible to write I2P applications in Java using the ministreaming library.
|
|
However, the Streaming library has superceded this, and provides better functionality.
|
|
</p>
|
|
|
|
<h3>Datagrams</h3>
|
|
<p><i>Not recommended</i></p>
|
|
The <a href="datagrams">Datagram library</a> allows sending UDP-like packets.
|
|
It's possible to use:
|
|
<ul>
|
|
<li>Repliable datagrams</li>
|
|
<li>Raw datagrams</li>
|
|
</ul>
|
|
|
|
<h3>I2CP</h3>
|
|
<p><i>Not recommended</i></p>
|
|
<p><a href="i2cp">I2CP</a> itself is a language independent protocol, but to implement an I2CP library
|
|
in something other than Java there is a significant amount of code to be written (encryption routines,
|
|
object marshalling, asynchronous message handling, etc). While someone could write an I2CP library in
|
|
C or something else, it would most likely be more useful to use the C SAM library instead.
|
|
</p>
|
|
|
|
<h3>Web Applications</h3>
|
|
I2P comes with the Jetty webserver, and configuring to use the Apache server instead is straightforward.
|
|
Any standard web app technology should work.
|
|
|
|
<h2 id="start">Start developing - a simple guide</h2>
|
|
Developing using I2P requires a working I2P installation and a development environment of your own choice.
|
|
If you are using Java, you can start development with the <a href="#start.streaming">streaming library</a> or datagram library.
|
|
Using another programming language, SAM or BOB can be used.
|
|
|
|
<h3 id="start.streaming">Developing with the streaming library</h3>
|
|
|
|
<p>
|
|
Development using the streaming library requires the following libraries in your classpath:
|
|
<ul>
|
|
<li>$I2P/lib/streaming.jar: the streaming library itself.</li>
|
|
<li>$I2P/lib/mstreaming.jar: the ministreaming library is used as the base for the streaming library.</li>
|
|
<li>$I2P/lib/i2p.jar: some standard I2P classes (like the Destination class) are very convenient when developing.</li>
|
|
</ul>
|
|
</p>
|
|
<p>
|
|
Network communication requires the usage of I2P network sockets.
|
|
To demonstrate this, we will create an application where a client can send text messages to a server,
|
|
who will print the messages and send them back to the client. In other words, the server will function as an echo.
|
|
</p>
|
|
<p>
|
|
We will start by initializing the server application. This requires getting an I2PSocketManager
|
|
and creating an I2PServerSocket.
|
|
In addition, we will ask the I2PSocketManager for an I2PSession, so we can find out the Destination we use.
|
|
</p>
|
|
<div class="box">
|
|
<pre>
|
|
package i2p.echoserver;
|
|
|
|
import net.i2p.client.I2PSession;
|
|
import net.i2p.client.streaming.I2PServerSocket;
|
|
import net.i2p.client.streaming.I2PSocketManager;
|
|
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
|
|
|
public class Main {
|
|
|
|
public static void main(String[] args) {
|
|
//Initialize application
|
|
I2PSocketManager manager = I2PSocketManagerFactory.createManager();
|
|
I2PServerSocket serverSocket = manager.getServerSocket();
|
|
I2PSession session = manager.getSession();
|
|
System.out.println(session.getMyDestination().toBase64()); //Print the base64 string, the regular string would look like garbage.
|
|
//The additional main method code comes here...
|
|
}
|
|
|
|
}
|
|
</pre>
|
|
<br /><br />
|
|
<center>Code example 1: initializing the server application.</center>
|
|
</div>
|
|
<p>
|
|
Once we have an I2PServerSocket, we can create I2PSocket instances to accept connections from clients.
|
|
In this example, we will create a single I2PSocket instance, that can only handle one client at a time.
|
|
A real server would have to be able to handle multiple clients.
|
|
To do this, multiple I2PSocket instances would have to be created, each in separate threads.
|
|
Once we have created the I2PSocket instance, we read data, print it and send it back to the client.
|
|
The bold code is the new code we add.
|
|
</p>
|
|
<div class="box">
|
|
<pre>
|
|
package i2p.echoserver;
|
|
|
|
</pre>
|
|
<b><pre>
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.net.ConnectException;
|
|
import java.net.SocketTimeoutException;
|
|
import net.i2p.I2PException;
|
|
import net.i2p.client.streaming.I2PSocket;
|
|
import net.i2p.util.I2PThread;
|
|
</pre></b>
|
|
<pre>
|
|
import net.i2p.client.I2PSession;
|
|
import net.i2p.client.streaming.I2PServerSocket;
|
|
import net.i2p.client.streaming.I2PSocketManager;
|
|
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
|
|
|
public class Main {
|
|
|
|
public static void main(String[] args) {
|
|
I2PSocketManager manager = I2PSocketManagerFactory.createManager();
|
|
I2PServerSocket serverSocket = manager.getServerSocket();
|
|
I2PSession session = manager.getSession();
|
|
System.out.println(session.getMyDestination().toBase64()); //Print the base64 string, the regular string would look like garbage.
|
|
</pre>
|
|
<b><pre>
|
|
|
|
//Create socket to handle clients
|
|
I2PThread t = new I2PThread(new ClientHandler(serverSocket));
|
|
t.setName("clienthandler1");
|
|
t.setDaemon(false);
|
|
t.start();
|
|
}
|
|
|
|
private static class ClientHandler implements Runnable {
|
|
|
|
public ClientHandler(I2PServerSocket socket) {
|
|
this.socket = socket;
|
|
}
|
|
|
|
public void run() {
|
|
while(true) {
|
|
try {
|
|
I2PSocket sock = this.socket.accept();
|
|
if(sock != null) {
|
|
BufferedReader br = new BufferedReader(new InputStreamReader(sock.getInputStream())); //Receive from clients
|
|
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream())); //Send to clients
|
|
String line = br.readLine();
|
|
if(line != null) {
|
|
System.out.println("Received from client: " + line);
|
|
bw.write(line);
|
|
bw.flush(); //Flush to make sure everything got sent
|
|
}
|
|
sock.close();
|
|
}
|
|
} catch (I2PException ex) {
|
|
System.out.println("General I2P exception!");
|
|
} catch (ConnectException ex) {
|
|
System.out.println("Error connecting!");
|
|
} catch (SocketTimeoutException ex) {
|
|
System.out.println("Timeout!");
|
|
} catch (IOException ex) {
|
|
System.out.println("General read/write-exception!");
|
|
}
|
|
}
|
|
}
|
|
|
|
private I2PServerSocket socket;
|
|
|
|
}
|
|
|
|
}
|
|
</pre></b>
|
|
<br /><br />
|
|
<center>Code example 2: accepting connections from clients and handling messages.</center>
|
|
</div>
|
|
|
|
<p>
|
|
When you run the above server code, it should print something like this (but without the line endings, it should just be
|
|
one huge block of characters):
|
|
<pre id="start.streaming.destination">
|
|
y17s~L3H9q5xuIyyynyWahAuj6Jeg5VC~Klu9YPquQvD4vlgzmxn4yy~5Z0zVvKJiS2Lk
|
|
poPIcB3r9EbFYkz1mzzE3RYY~XFyPTaFQY8omDv49nltI2VCQ5cx7gAt~y4LdWqkyk3au
|
|
6HdfYSLr45zxzWRGZnTXQay9HPuYcHysZHJP1lY28QsPz36DHr6IZ0vwMENQsnQ5rhq20
|
|
jkB3iheYJeuO7MpL~1xrjgKzteirkCNHvXN8PjxNmxe-pj3QgOiow-R9rEYKyPAyGd2pe
|
|
qMD-J12CGfB6MlnmH5qPHGdZ13bUuebHiyZ1jqSprWL-SVIPcynAxD2Uu85ynxnx31Fth
|
|
nxFMk07vvggBrLM2Sw82pxNjKDbtO8reawe3cyksIXBBkuobOZdyOxp3NT~x6aLOxwkEq
|
|
BOF6kbxV7NPRPnivbNekd1E1GUq08ltDPVMO1pKJuGMsFyZC4Q~osZ8nI59ryouXgn97Q
|
|
5ZDEO8-Iazx50~yUQTRgLMOTC5hqnAAAA
|
|
</pre>
|
|
This is the base64-representation of the server Destination. The client will need this string to reach the server.
|
|
</p>
|
|
<p>
|
|
Now, we will create the client application. Again, a number of steps are required for initialization.
|
|
Again, we will need to start by getting an I2PSocketManager.
|
|
We won't use an I2PSession and an I2PServerSocket this time.
|
|
Instead, we will use the server Destination string to start our connection.
|
|
We will ask the user for the Destination string, and create an I2PSocket using this string.
|
|
Once we have an I2PSocket, we can start sending and receiving data to and from the server.
|
|
</p>
|
|
<div class="box">
|
|
<pre>
|
|
package i2p.echoclient;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.BufferedWriter;
|
|
import java.io.IOException;
|
|
import java.io.InputStreamReader;
|
|
import java.io.InterruptedIOException;
|
|
import java.io.OutputStream;
|
|
import java.io.OutputStreamWriter;
|
|
import java.net.ConnectException;
|
|
import java.net.NoRouteToHostException;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
import net.i2p.I2PException;
|
|
import net.i2p.client.streaming.I2PSocket;
|
|
import net.i2p.client.streaming.I2PSocketManager;
|
|
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
|
import net.i2p.data.DataFormatException;
|
|
import net.i2p.data.Destination;
|
|
|
|
public class Main {
|
|
|
|
public static void main(String[] args) {
|
|
I2PSocketManager manager = I2PSocketManagerFactory.createManager();
|
|
System.out.println("Please enter a Destination:");
|
|
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
|
|
String destinationString = null;
|
|
try {
|
|
destinationString = br.readLine();
|
|
} catch (IOException ex) {
|
|
System.out.println("Failed to get a Destination string.");
|
|
return;
|
|
}
|
|
Destination destination = null;
|
|
try {
|
|
destination = new Destination(destinationString);
|
|
} catch (DataFormatException ex) {
|
|
System.out.println("Destination string incorrectly formatted.");
|
|
return;
|
|
}
|
|
I2PSocket socket = null;
|
|
try {
|
|
socket = manager.connect(destination);
|
|
} catch (I2PException ex) {
|
|
System.out.println("General I2P exception occurred!");
|
|
} catch (ConnectException ex) {
|
|
System.out.println("Failed to connect!");
|
|
} catch (NoRouteToHostException ex) {
|
|
System.out.println("Couldn't find host!");
|
|
} catch (InterruptedIOException ex) {
|
|
System.out.println("Sending/receiving was interrupted!");
|
|
}
|
|
try {
|
|
//Write to server
|
|
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
|
|
bw.write("Hello I2P!\n");
|
|
bw.flush(); //Flush to make sure everything got sent
|
|
//Read from server
|
|
BufferedReader br2 = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
|
String s = null;
|
|
while ((s = br2.readLine()) != null) {
|
|
System.out.println("Received from server: " + s);
|
|
}
|
|
socket.close();
|
|
} catch (IOException ex) {
|
|
System.out.println("Error occurred while sending/receiving!");
|
|
}
|
|
}
|
|
|
|
}
|
|
</pre>
|
|
<br /><br />
|
|
<center>Code example 3: starting the client and connecting it to the server application.</center>
|
|
</div>
|
|
<p>
|
|
Finally, you can run both the server and the client application.
|
|
First, start the server application. It will print a Destination string (like shown <a href="#start.streaming.destination">above</a>).
|
|
Next, start the client application. When it requests a Destination string, you can enter the string printed by the server.
|
|
The client will then send 'Hello I2P!' (along with a newline) to the server, who will print the message and send it back to the client.
|
|
</p>
|
|
<p>
|
|
Congratulations, you have successfully communicated over I2P!
|
|
</p>
|
|
|
|
<h2>Existing Applications in Development</h2>
|
|
Contact us if you would like to help.
|
|
<ul>
|
|
<li>
|
|
<a href="http://syndie.i2p2.de/">Syndie</a>
|
|
<li>
|
|
<a href="http://forum.i2p/viewforum.php?f=25">I2Phex</a> - contact Complication
|
|
<a href="http://forum.i2p2.de/viewforum.php?f=25">(outside I2P)</a>
|
|
<li>
|
|
<a href="http://www.imule.i2p/">IMule</a>
|
|
<li>I2PRufus - contact codevoid
|
|
<li>I2P-BT - contact sponge
|
|
<li>BOB - contact sponge
|
|
</ul>
|
|
|
|
<h2>Application Ideas</h2>
|
|
<ul>
|
|
<li>NNTP server - there have been some in the past, none at the moment
|
|
<li>Jabber server - there have been some in the past, none at the moment
|
|
<li>PGP Key server and/or proxy
|
|
<li>Download manager / eepget scheduler -
|
|
We use eepget to fetch lots of things reliably over i2p, and there's already an
|
|
implementation of a sequential download manager (net.i2p.util.EepGetScheduler),
|
|
but there isn't any sort of user interface to it. A web based UI would be
|
|
great.
|
|
<li>Content Distribution / DHT applications - help out with <a href="http://feedspace.i2p/">feedspace</a>,
|
|
port dijjer, look for alternatives
|
|
<li>Help out with <a href="http://syndie.i2p2.de/">Syndie</a> development
|
|
<li>Web-based applications - The sky is the limit for hosting web-server-based
|
|
applications such as blogs, pastebins, storage, tracking, feeds, etc.
|
|
Any web or CGI technology such as Perl, PHP, Python, or Ruby will work.
|
|
<li>Resurrect some old apps - in the i2p source package -
|
|
bogobot, pants, proxyscript, q, stasher, socks proxy, i2ping
|
|
</ul>
|
|
|
|
{% endblock %}
|