¿Porqué escribir código específico para I2P?
Hay múltiples formas de usar aplicaciones en I2P. Usando los I2PTunnel puede utilizar aplicaciones normales sin que los programas tengan que tener soporte explícito para i2P. Esto es muy eficiente en escenarios cliente-servidor, cuando se necesita conectar con una sola web. Simplemente puede crear un túnel usando I2PTunnel para conectarse a la web, como se puede ver en la Figura 1.
Si su aplicación es distribuida, requerirá la conexión de un gran número de pares. Usando un I2PTunnel necesitaría crear un túnel por cada para que quiera conectar, como se ve en la Figura 2. Este procese puede ser automatizado, por supuesto, pero ejecutar muchas instancias de I2PTunnel crea mucha sobrecarga. Además, con muchos protocolos tendría que forzar a todos a usar los mismos puertos para todos los pares - por ejemplo, si desea habilitar DCC, todos necesitarían llegar al acuerdo de que el puerto 10001 es para Alice, el puerto 10002 para Bob, el puerto 10003 para Charlie, y así, ya que el protocolo incluye información específica TCP/IP (servidor y puerto).
Las aplicaciones de red habitualmente envían mucha información adicional que puede identificar a los usuarios. Los nombres de los hosts, puertos, horarios, etc, a veces son enviados sin informar al usuario. Por lo que el diseñar los protocolos de red con el anonimato en mente puede evitar comprometer las identidades de los usuarios.
También hay que tomar consideraciones sobre la eficiencia cuando se determina como funcionar sobre I2P. La librería de streaming y las cosas construidas sobre ellas funcionan con handshakes similares a los de TCP, mientras que los protocolos del núcleo de I2P (I2NP e I2CP) son estrictamente basados en mensajes (como UDP). La diferencia más importante es que con I2P las comunicaciones funcionan sobre una red grande y gorda, cada mensaje de fin a fin tendrá latencias no triviales, y puede contener cargas de varios KB. Una aplicación que necesite una simple petición y respuesta puede evitar cualquier estado y evitar los retardos con los handshakes de inicio usando (mejor esfuerzo) datagramas sin tener que preocuparse sobre la detección de MTU o la fragmentación de los mensajes.
Figura 1: Crear una conexión servidor-cliente usando un I2PTunnel sólo requiere crear un solo túnel.
Fugura 2: Configurar conexiones para aplicaciones p2p requiere un gran número de túneles.
En resumen, hay bastantes razones para escribir el código específico para I2P:
- Crear un gran número de I2PTunnles consume una cantidad de recursos nada triviales, lo cual es problemático para las aplicaciones distribuidas (hace falta un nuevo túnel por cada par).
- Los protocolos de red generales a menudo envían mucha información adicional que puede usarse para identificar a los usuarios. Programar específicamente para I2P permite la creación de protocolos de red que no filtran dicha información no deseada, permitiendo que los usuarios permanezcan seguros y anónimos.
- Los protocolos de red diseñados para el Internet normal pueden ser ineficientes sobre I2P, que es una red con una latencia mayor.
I2P soporta un interfaz de plugins estándar para los desarrolladores, con lo que las aplicaciones pueden ser integradas y distribuidas más fácilmente.
Las aplicaciones escritas en Java y accesibles/ejecutables usando un interfaz HTML a través de aplicaciones web estándar pueden considerarse para incluirse en la distribución I2P.
Conceptos importantes
Hay varios cambios que se requieren ajustar cuando se usa I2P.
Destinación ~= host+puerto
Una aplicación ejecutándose sobre I2P envía un mensaje desde, y recibe mensajes hacia un único punto seguro criptográfico. - una "destinación". En términos de TCP y UDP una destinación puede ser considerada (en gran parte) el equivalente a un hostname más el par de números de puertos, aunque hay unas pocas diferencias.
- Una destinación I2P es en sí misma una construcción criptográfica - toda la información enviada es cifrada tal y como si hubiera una implementación mundial de IPsec con la localización (anónima) del punto final firmado como si existiera una implementación universal de DNSSEC.
- Los destinos I2P son identificadores móviles - se pueden mover de un router I2P a otro (o incluso se puede alojar de forma redundante (multihome) - operar desde varios routers I2P a la vez). Esto es bastante diferente a como funcionan TCP o UDP, donde un sólo extremo (puerto) tiene que estar en un único equipo.
-
Las destinaciones I2P son largas y feas - en el fondo contienen un clave pública ElGamal de 2048 bits para el cifrado, una calve pública DSA de 1024 bits para firmar, y un certificado de tamaño variable, que puede contener una prueba de trabajo realizado o información blindada.
Hay formas de referirse a esa larga y fea destinación acortándola con nombres bonitos (por ejemplo "irc.duck.i2p"), pero estas técnicas no garantizan una unicidad global (ya que están almacenadas localmente en la base de datos de la máquina de cada persona) y el mecanismo actual no es especialmente escalable o seguro (las actualizaciones de la lista de hosts son manejadas usando "suscripciones" a servicios de nombres). Quizás algún día habrá un sistema más seguro, legible por humanos, escalable y con unicidad global, pero las aplicaciones no deberían depender de su existencia, ya que hay gente que piensa que ese tipo de bestia no es posible. Hay disponible más información sobre el sistema de nombres.
Mientras que la mayoría de las aplicaciones no necesitan distinguir protocolos o puertos, I2P si los soporta. Las aplicaciones complejas pueden especificar un protocolo, desde un puerto, y hacia un puerto, en un mensaje, hacia tráfico múltiple o sobre una sola destinación. Vea la página de datagrama para más detalles. Las aplicaciones simples funciona escuchando por "todos los protocolos" sobre "todos los puertos" de una destinación.
Anonimato y confidencialidad.
I2P tiene cifrado transparente y autenticación transparente de todos los datos que pasan por la red - si Bob envía a la destinación de Alice, sólo la destinación de Alice puede recibirlo, y si Bob está usando la librería de datagramas o streaming, Alice sabe seguro que la destinación de Bob es la que envió los datos.
Por supuesto, I2P anonimiza transparentemente los datos enviados entre Alice y Bob, pero no hace nada para anonimizar el contenido de lo que envían. Por ejemplo, si Alice envía a Bob un formulario con su nombre completo, ID del gobierno, y los números de sus tarjetas de crédito, no hay nada que I2P pueda hacer. Por eso, los protocolos y las aplicaciones deben tener en cuenta qué información están intentando proteger y qué información desean exponer.
Los datagramas I2P pueden ser hasta de varios KB
Las aplicaciones que usan datagramas I2P (ya sean en bruto o respondibles) pueden entenderse en términos de UDP - los datagramas no están ordenados, mejor esfuerzo, y sin-conexión - pero al contrario que UDP, las aplicaciones no tienen que preocuparse de la detección MTU y pueden simplemente enviar datagramas grandes. Cuando normalmente el límite superior es de 32 KB, el mensaje es troceado para el transporte, bajando la confiabilidad del todo. No se recomiendan datagramas de más de 10 KB. Ve la página de los datagramas para más detalles. Para muchas aplicaciones, esos 10 KB de datos es suficiente para una petición o respuesta completa, permitiendoles operar transparentemente en I2P como una aplicación tipo UDP si tener que crear fragmentaciones, re-eenvíos, etc.
Opciones de desarrollo
Hay varias formas de enviar datos sobre I2P, cada una con sus ventajas e inconvenientes. La librería de streaming es el interfaz recomendado, usado por la mayoría de las aplicaciones de I2P.
Librería Streaming
La librería de streaming completa es ahora mismo el interfaz estándar. Permite programar usando sockets al estilo TCP, como se explica en la guía de desarrollo de Streaming.
BOB
BOB is the Basic Open Bridge, allowing an application in any language to make streaming connections to and from I2P. At this point in time it lacks UDP support, but UDP support is planned in the near future. BOB also contains several tools, such as destination key generation, and verification that an address conforms to I2P specifications. Up to date info and applications that use BOB can be found at this I2P Site.
SAM, SAM V2, SAM V3
No se recomienda SAM, SAM v2 vale, pero se recomienda SAM v3
SAM es el protocolo de Simple Anonymous Messaging, mensajería anónima simple, que permite a una aplicación escrita en cualquier lenguaje entenderse con el puente SAM a través de sockets TCP y en donde ese puente multiplexa todo su tráfico I2P, coordinando el cifrado/descifrado transparentemente y el manejo de los eventos. SAM soporta tres tipos de operaciones:
- streams, para cuando Alice y Bob quieren enviar datos de uno a otro seguramente y en orden
- datagramas respondibles, para cuando Alice quiere enviar a Bob un mensaje al que Bob puede responder.
- datagramas en bruto, para cuando Alice quiere ahorrar el máximo ancho de banda y obtener el mejor rendimiento, y a Bob no le importa si la información del remitente está autentificada o no (por ejemplo cuando los datos transferidos ya son auto autentificables)
SAM V3 apunta hacia la misma meta que SAM y SAM V2, pero no requiere multiplexado/demultiplexado. Cada flujo (`stream`) I2P es gestionado por su propio socket entre la aplicación y la pasarela (`bridge`) SAM. Además, pueden ser enviados y recibidos datagramas mediante la aplicación a través de comunicaciones de datagrama con la pasarela SAM.
SAM V2 es una nueva versión usada por imule que
resuelve alguno de los problemas en SAM.
SAM V3 es usado desde la versión 1.4.0 de imule.
I2PTunnel
La aplicación I2PTunnel permite a otras aplicaciones establecer túneles específicos tipo-TCP hacia los pares ('peers'), creando tanto aplicaciones I2PTunnel 'cliente' (que escuchan en un puerto específico y se conectan a un destino I2P específico cuando un socket hacia ese puerto se abre) como aplicaciones I2PTunnel 'servidor' (que escuchan un destino I2P específico y cuando reciben una nueva conexión I2P se interponen repitiéndola hacia un anfitrión('host')/puerto específico). Estos flujos ('streams') son puros de 8-bits ('8-bit clean') y son autentificados y asegurados a través de la misma librería que usa SAM, pero crear múltiples instancias I2PTunnel únicas reviste una sobrecarga no trivial, ya que cada una tiene su propio destino único I2P y su propio grupo de túneles, claves, etc.
SOCKS
I2P soporta un proxy SOCKS V4 y V5. Las conexiones salientes funcionan bien. La funcionalidad de las conexiones entrantes (servidor) y UDP puede ser incompleta y no testada.
Ministreaming
Eliminado
Suele haber una librería sencilla "ministreaming", pero ministreaming.jar ahora contiene sólo las interfaces para la librería streaming completa.
Datagramas
Recomendado para aplicaciones tipo-UDP
La librería Datagram permite enviar paquetes tipo-UDP. Es posible usar:
- datagramas respondibles
- Datagramas crudos ('raw')
I2CP
No recomendado
I2CP en si mismo es un protocolo independiente del lenguaje, pero para implementar una librería I2CP en cualquier otra cosa que no sea Java hay una cantidad de código significativa a escribir (rutinas de cifrado, serialización ('marshalling') de objetos, gestión de mensajes asíncronos, etc.). Aunque cualquiera pueda escribir una librería I2CP en C o algún otro lenguaje, probablemente sería más útil usar la librería C SAM en su lugar.
Aplicaciones web
I2P viene con el servidor web Jetty, pero configurar el servidor Apache para usarlo en su lugar también es apropiado. Cualquier tecnología de aplicaciones web estandar debería funcionar.
Empiece a programar - una guía simple
Desarrollar usando I2P requiere una instalación I2P funcional y un entorno de desarrollo de su propia elección. Si esta usando Java puede iniciar el desarrollo con la librería streaming o la librería datagram. Usando otro lenguaje de programación, se puede usar SAM o BOB.
Desarrollo con la librería streaming
El siguiente ejemplo muestra como crear aplicaciones cliente y servidor tipo-TCP usando la librería streaming.
Esto requerirá las siguientes librerías en su ruta de clases ('classpath'):
- $I2P/lib/streaming.jar: La librería streaming en si misma
- $I2P/lib/mstreaming.jar: El patrón Factory e interfaces para la librería streaming.
- $I2P/lib/i2p.jar: Clases I2P estandar, estructuras de datos, API y utilidades.
Puede obtenerlas desde una instalación de I2P, o añadir las siguientes dependencias desde Maven Central:
net.i2p:i2p:2.6.1
net.i2p.client:streaming:2.6.1
La comunicación de red requiere el uso de sockets de red I2P. Para demostrar esto, crearemos una aplicación donde un cliente pueda enviar mensajes de texto a un servidor, que imprimirá los mensajes y los enviará de vuelta al cliente. En otras palabras, el servidor funcionará como un eco.
Comenzaremos por inicializar la aplicación servidor. Esto requiere obtener un gestor de socket I2P ('I2PSocketManager') y crear un socket de servidor I2P ('I2PServerSocket'). No proporcionaremos el patrón I2PSocketManagerFactory con las claves guardadas para un Destino existente, por lo que creará un nuevo Destino para nosotros. Así que pediremos al gestor de socket I2P ('I2PSocketManager') una sesión I2P ('I2PSession'), para que podamos averiguar el Destino que fue creado, ya que necesitaremos copiar y pegar esa información más adelante para que el cliente pueda conectarse con nosotros.
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();
//Print the base64 string, the regular string would look like garbage.
System.out.println(session.getMyDestination().toBase64());
//The additional main method code comes here...
}
}
Ejemplo de código 1: inicialización de la aplicación servidor.
Una vez que tengamos un socket de servidor I2P ('I2PServerSocket'), podemos crear instancias de socket I2P ('I2PSocket') para aceptar conexiones desde los clientes. En este ejemplo crearemos una única instancia I2PSocket, que sólo puede gestionar un cliente cada vez. Un servidor real tendría que ser capaz de gestionar múltiples clientes. Para hacer esto, múltiples instancias I2PSocket tendrían que ser creadas, en hilos separados cada una. Una vez hayamos creado la instancia I2PSocket, leemos los datos y los imprimimos y enviamos de vuelta al cliente. El código en negrita es el código nuevo que añadimos.
package i2p.echoserver;
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;
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();
//Print the base64 string, the regular string would look like garbage.
System.out.println(session.getMyDestination().toBase64());
//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) {
//Receive from clients
BufferedReader br = new BufferedReader(new InputStreamReader(sock.getInputStream()));
//Send to clients
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream()));
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;
}
}
Ejemplo de código 2: aceptar conexiones desde clientes y gestionar mensajes.
Cuando ejecute el código servidor de arriba, debería imprimir algo como esto (pero sin los finales de línea, debería ser sólo un enorme bloque de caracteres):
y17s~L3H9q5xuIyyynyWahAuj6Jeg5VC~Klu9YPquQvD4vlgzmxn4yy~5Z0zVvKJiS2Lk poPIcB3r9EbFYkz1mzzE3RYY~XFyPTaFQY8omDv49nltI2VCQ5cx7gAt~y4LdWqkyk3au 6HdfYSLr45zxzWRGZnTXQay9HPuYcHysZHJP1lY28QsPz36DHr6IZ0vwMENQsnQ5rhq20 jkB3iheYJeuO7MpL~1xrjgKzteirkCNHvXN8PjxNmxe-pj3QgOiow-R9rEYKyPAyGd2pe qMD-J12CGfB6MlnmH5qPHGdZ13bUuebHiyZ1jqSprWL-SVIPcynAxD2Uu85ynxnx31Fth nxFMk07vvggBrLM2Sw82pxNjKDbtO8reawe3cyksIXBBkuobOZdyOxp3NT~x6aLOxwkEq BOF6kbxV7NPRPnivbNekd1E1GUq08ltDPVMO1pKJuGMsFyZC4Q~osZ8nI59ryouXgn97Q 5ZDEO8-Iazx50~yUQTRgLMOTC5hqnAAAAEsta es la representación-base64 del servidor Destino. El cliente necesitará esta cadena para alcanzar el servidor.
Ahora crearemos la aplicación cliente. Otra vez se requiere un determinado número de pasos para la inicialización. De nuevo, necesitaremos comenzar obteniendo un gestor de socket I2P ('I2PSocketManager'). No usaremos una sesión I2P ('I2PSession') ni un socket de servidor I2P ('I2PServerSocket') esta vez. En su lugar usaremos la cadena del servidor Destino para iniciar nuestra conexión. Preguntaremos al usuario por la cadena Destino, y crearemos un socket I2P ('I2PSocket') usando esta cadena. Una vez que tengamos el I2PSocket podemos comenzar a enviar y recibir datos hacia y desde el servidor.
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 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;
try {
destinationString = br.readLine();
} catch (IOException ex) {
System.out.println("Failed to get a Destination string.");
return;
}
Destination destination;
try {
destination = new Destination(destinationString);
} catch (DataFormatException ex) {
System.out.println("Destination string incorrectly formatted.");
return;
}
I2PSocket socket;
try {
socket = manager.connect(destination);
} catch (I2PException ex) {
System.out.println("General I2P exception occurred!");
return;
} catch (ConnectException ex) {
System.out.println("Failed to connect!");
return;
} catch (NoRouteToHostException ex) {
System.out.println("Couldn't find host!");
return;
} catch (InterruptedIOException ex) {
System.out.println("Sending/receiving was interrupted!");
return;
}
try {
//Write to server
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("Hello I2P!\n");
//Flush to make sure everything got sent
bw.flush();
//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!");
}
}
}
Ejemplo de código 3: iniciar el cliente y conectarlo a la aplicación servidor.
Finalmente puede ejecutar ambas aplicaciones cliente y servidor. Primero, inicie la aplicación servidor. Imprimirá una cadena Destino (como la mostrada arriba). Después inicie la aplicación cliente. Cuando esta solicite una cadena Destino, puede introducir la cadena impresa por el servidor. Entonces el cliente enviará 'Hello I2P!' (junto con un caracter de nueva línea ('newline')) al servidor, que imprimirá el mensaje y lo enviará de vuelta al cliente.
¡Felicidades!, se ha comunicado con éxito sobre I2P
Aplicaciones existentes
Contáctenos si quiere contribuír.
See also all the plugins on plugins.i2p, the applications and source code listed on echelon.i2p, and the application code hosted on git.repo.i2p.
See also the bundled applications in the I2P distribution - SusiMail and I2PSnark.
Ideas para aplicaciones
- Servidor NNTP - ha habido algunos en el pasado, ninguno en este momento
- Servidor Jabber - ha habido algunos en el pasado, y hay uno en este momento con acceso a la Internet pública
- Servidor de claves PGP y/o proxy
- Distribución de contenidos / Aplicaciones DHT - resucite Feedspace, porte Dijjer, busque alternativas
- Contribuya al desarrollo de Syndie
- Aplicaciones basadas-en-web - El cielo es el límite para el hospedaje de aplicaciones basadas-en-servidor-web tales como blogs, tablones de anotaciones ('pastebins'), almacenamiento, seguimiento, actualización de publicaciones ('feeds'), etc. Cualquier web o tecnología CGI como Perl, PHP, Python o Ruby funcionará.
- Resucite algunas aplicaciones viejas, varias presentes previamente en el paquete fuente de I2P - Bogobot, Pants, Proxyscript, Q, Stasher, Socks Proxy, I2Ping, Feedspace