Skip to content
/ Py2p Public

I2P SAM v3.3 – Python library (fork of Py2p by Robin <robin@mail.i2p> https://i2pgit.org/robin/Py2p)

License

Notifications You must be signed in to change notification settings

i2pray/Py2p

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Py2p

This is a Python interface to the I2P network. It works by implementing I2P's Simple Anonymous Messaging (SAM) protocol in a form that is easy to deal with. Py2p is a complete rewrite and extension of the i2plib package. It presents a simple object-oriented interface that works with the Python asyncio package for asynchronous operation using co-routines.

Prerequisites

  • Python version 3.5 or later
  • SAM version 3.3 or later
  • By default, I2P routers do not enable the SAM bridge. You have to turn it on using the router console.
  • You need to import the asyncio package and be familiar with co-routine programming in Python.

Installation

Just put the py2p.py file in a location where your import statement can find it.

Example

Here is a function that uses Py2p to fetch a page of HTML from an eepsite:

import py2p, asyncio
async def httptest():
    async with py2p.Access() as a1:
        async with a1.stream('i2p-projekt.i2p') as s:
            s.send(b'GET /en/faq/ HTTP/1.1\n\n')
            await s.drain()
        	    text = await s.read(5000)
            while text:
                print(text.decode(), end='')
        		    text = f.read(5000)
            await s.shutdown()
        print("EOF")
        await a1.shutdown()

1.0 Concepts

The SAM protocol is the recommended mechanism for non-Java programs to interact with an I2P Router. Since its creation in 2015, SAM has undergone several revisions, adding new features and sometimes new ways of accessing old features. Some of the old methods are now deprecated.

Py2p provides a simple interface to only the most recent ways of doing things as of SAM Version 3.3. The goal is to make it easy to write simple programs that directly access the main features of the I2P network. It takes care of generating and parsing the SAM protocol messages and manages ports, sockets, and addresses.

1.1 Addresses

The I2P network is designed to provide anonymous communication. One aspect of this is that it uses a cryptographic form of addressing that does not identify where a participant is located, or who they are. These addresses can be quite long, up to nearly 800 bytes in binary, and even longer when written in base64. There is a shorter notation called a 'b32' address, about XX bytes long. In addition, the network provides a name lookup function so that huaman-readable names like 'i2p-projekt.i2p' can be translated into the full base64 notation. The Py2p package supports all these forms of addressing and for the most part hides the details.

1.2 Access to the I2P network

There are two levels of using the I2P network with Py2p: 1) gaining access to an I2P Router, which also entails generating or providing the address by which other computers can reach yours, and then 2) operating one or more communication channels to other computers on the network.

1.3 Communication channels

To send and receive data you need to create one or more channels within the access session. There are three kinds of channel and each one represents a SAM sub-session. These are:

  1. A stream channel acts much like an internet TCP connection. Transmissions are guaranteed to arrive at the destination, and in the same order in which they were sent. There is considerable overhead in ensuring these deliveries however.

  2. A repliable channel carries individual datagrams, like UDP on the internet. There are no guarantees of delivery, or that messages are delivered in the correct order, but the delivery will be much faster. (Experiments show an improvement of at least a factor of five over a stream channel for short messages.)

  3. An anonymous channel is like a repliable channel except that the sender's address is not provided to the receiver. A common design pattern is for clients to use repliable datagrams to send requests to a server, each containing a unique message identifier. The server then responds with anonymous datagrams that carry the same identifier.

You should be careful to call the shutdown() method on each channel before the controlling Access context ends.

2.0 The Python classes

Py2p takes advantage of Python asynchronous context managers and you will be using the async with construction extensively.

2.1 The Access class

This class represents a connection to an I2P router, using what the SAM documentation calla a PRIMARY SESSION. It must be created using a Python async with statement. The parameters, all optional, are:

  • host: The name or IP address of your I2P router, default 127.0.0.1.
  • port: The TCP port of the SAM bridge in the router. Default 7656.
  • udp_port: The UDP port of the SAM bridge for repliable and anonymous datagrams. Default 7655.
  • address: The I2P address to be associated with all communication over this session. If none is supplied, a transient address will be created. This can be a base64 string, or a Destination object.

For example, the following code starts an I2P session with the router located elsewhere on a local network. The session will start when control enters the scope of the with statement and should be closed before control leaves that scope:

    async with py2p.Access( host='192.168.1.20' ) as a:
        # Code that uses `a` ...
        await a.shutdown()

While the Python async with statement does have a mechanism for cleaning up at the end, it is not reliable in all situations, especially where Exceptions are raised. So Py2p requires you to explicitly shutdown each context.

The following attributes are available on an Access object within the scope of the with:

  • address() returns the base64 address associated with the session. If no address was specified on the initial Access object, this will be the address that was created. If you are writing a server you will want to save this address to use in future sessions so that your address does not change.

These methods are also available:

  • shutdown() To gracefully close the connection. This must be the last call made within the scope of the async with Access statement. It needs an await.

  • lookup( address ) To look up an I2P address string or node name and return a Destination object (see below). This is an asynchronous function and must be called with await. For example:

    async with py2p.Access( host='192.168.1.20' ) as a:
        addr = await a.lookup('zzz.i2p')
        ## Use addr
        await a.shutdown()

Normally you will not need to use lookup because SAM usually does it for you.

There are additional functions to create the various kinds of Channels described below.

2.2 The Stream channel class

A stream acts much like a TCP network connection. You create a stream channel within an Access session using the stream method. It has one optional parameter of the I2P address to connect to. If omitted, the channel will wait for an incoming connection. For example, this demonstrates creating an I2P session and connecting a stream to the node '`foo.i2p':

    async with Access() as a:
        async with a.stream( 'foo.i2p' ) as foo:
            ## Use stream 'foo'...
            await foo.shutdown()
        await a.shutdown()

Once a stream channel has been connected, you can execute these methods on it:

  • send( data ) Transmits a bytes object to the other end of the stream. Note that this is a buffered operation and the data may not all be transmitted by the time this function returns.

  • drain() Forces the transmission of any pending data from send operations. This is an asynchronous operation and so must be called with await.

  • read( maxsize ) retrieves data from the stream. There is one optional parameter of the maximum number of bytes to receive. The default is -1 which means there is no limit. This function can block and so must have an await in front of it.

  • address() returns the I2P address of the other end of the stream.

  • shutdown() gracefully closes the stream. This must be called with await.

2.3 The Repliable datagram channel class

A repliable datagram behaves much like a internet UDP datagram. Reliable delivery is not guaranteed and datagrams might not be delivered in the same order in which they were sent. The advantage over using a stream, especially for short messages, is that messages arrive much more quickly. Each message sent over this channel will arrive annotated with the I2P address of the program that sent it. To create a Repliable Channel, use the repliable() function of the Access object.

    async with Access() as a:
        async with a.repliable() as r:
            ## Send and receive datagrams on 'r'...
            await r.shutdown()
        await a.shutdown()
  • port() Return the UDP port number

  • send( data, address) Transmits a bytes object to a specified destination.

  • receive() Waits for the arrival of a datagram, and returns a tuple of that bytes object along with the base64 address of the sender. This must be called with the await modifier.

  • shutdown() Gracefully closes the channel. This is asynchronous so requires the await directive.

2.4 The Anonymous datagram channel class

An anonymous datagram is similar to a repliable datagram except that the address of the sender is not available to the receiver. Thus the overhead is less.

    async with Access() as a:
        async with a.anonymous() as x:
            ## Send and receive datagrams on 'x'...
            await x.shutdown()
        await a.shutdown()
  • send( data, address) Transmits a bytes object to a specified destination.

  • receive() Waits for the arrival of a datagram, and returns a bytes object. This must be called with the await modifier.

  • shutdown() Gracefully closes the channel.

2.5 The Destination class

A Destination represents a full I2P network address, possibly including its private key. If you are only writing a 'client' program, you may not need to deal with this class at all.

  • base64 returns a base64-encoded representation of the I2P address.
  • has_private_key() returns True if a PrivateKey object has been associated with this Destination.

2.6 The PrivateKey class

Destination objects may optionally include a Private Key. The Private Key must be there in order for a Destination object to be used in in the address parameter when creating an Access.

3.0 Tests

About

I2P SAM v3.3 – Python library (fork of Py2p by Robin <robin@mail.i2p> https://i2pgit.org/robin/Py2p)

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published