New tunnel API
parent
891c864833
commit
bb82c7040a
|
@ -92,7 +92,8 @@ import i2plib
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
# making your local web server available in the I2P network
|
# making your local web server available in the I2P network
|
||||||
asyncio.ensure_future(i2plib.server_tunnel(("127.0.0.1", 80)))
|
tunnel = i2plib.ServerTunnel(("127.0.0.1", 80))
|
||||||
|
asyncio.ensure_future(tunnel.run())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
@ -110,7 +111,8 @@ import i2plib
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
# bind irc.echelon.i2p to 127.0.0.1:6669
|
# bind irc.echelon.i2p to 127.0.0.1:6669
|
||||||
asyncio.ensure_future(i2plib.client_tunnel(("127.0.0.1", 6669), "irc.echelon.i2p"))
|
tunnel = i2plib.ClientTunnel("irc.echelon.i2p", ("127.0.0.1", 6669))
|
||||||
|
asyncio.ensure_future(tunnel.run())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
|
12
docs/api.rst
12
docs/api.rst
|
@ -33,14 +33,16 @@ Utilities
|
||||||
Tunnel API
|
Tunnel API
|
||||||
----------
|
----------
|
||||||
|
|
||||||
Tunnel API is the quickest way to use regular programms inside I2P.
|
Tunnel API is the quickest way to use regular software inside I2P.
|
||||||
Client tunnel binds a remote I2P destination to a port on your local machine.
|
Client tunnel binds a remote I2P destination to a local address.
|
||||||
Server tunnel exposes a port on your local machine to the I2P network.
|
Server tunnel exposes a local address to the I2P network.
|
||||||
|
|
||||||
.. autofunction:: client_tunnel
|
|
||||||
.. autofunction:: server_tunnel
|
|
||||||
.. autoclass:: i2plib.tunnel.I2PTunnel
|
.. autoclass:: i2plib.tunnel.I2PTunnel
|
||||||
:members:
|
:members:
|
||||||
|
.. autoclass:: i2plib.ClientTunnel
|
||||||
|
:inherited-members:
|
||||||
|
.. autoclass:: i2plib.ServerTunnel
|
||||||
|
:inherited-members:
|
||||||
|
|
||||||
Data structures
|
Data structures
|
||||||
---------------
|
---------------
|
||||||
|
|
|
@ -39,8 +39,10 @@ def main(args):
|
||||||
http_server_thread.start()
|
http_server_thread.start()
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
asyncio.ensure_future(i2plib.server_tunnel(server_address,
|
|
||||||
loop=loop, private_key=priv.base64, sam_address=sam_address), loop=loop)
|
tunnel = i2plib.ServerTunnel(server_address,
|
||||||
|
loop=loop, private_key=priv, sam_address=sam_address)
|
||||||
|
asyncio.ensure_future(tunnel.run(), loop=loop)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
|
|
@ -92,7 +92,8 @@ Expose a local service to I2P like that:
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
# making your local web server available in the I2P network
|
# making your local web server available in the I2P network
|
||||||
asyncio.ensure_future(i2plib.server_tunnel(("127.0.0.1", 80)))
|
tunnel = i2plib.ServerTunnel(("127.0.0.1", 80))
|
||||||
|
asyncio.ensure_future(tunnel.run())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
@ -113,7 +114,8 @@ Bind a remote I2P destination to a port on your local host:
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
# bind irc.echelon.i2p to 127.0.0.1:6669
|
# bind irc.echelon.i2p to 127.0.0.1:6669
|
||||||
asyncio.ensure_future(i2plib.client_tunnel(("127.0.0.1", 6669), "irc.echelon.i2p"))
|
tunnel = i2plib.ClientTunnel("irc.echelon.i2p", ("127.0.0.1", 6669))
|
||||||
|
asyncio.ensure_future(tunnel.run())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
|
|
@ -14,7 +14,7 @@ from .aiosam import (
|
||||||
create_session, stream_connect, stream_accept
|
create_session, stream_connect, stream_accept
|
||||||
)
|
)
|
||||||
|
|
||||||
from .tunnel import client_tunnel, server_tunnel
|
from .tunnel import ClientTunnel, ServerTunnel
|
||||||
|
|
||||||
from .utils import get_sam_address
|
from .utils import get_sam_address
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
__title__ = 'i2plib'
|
__title__ = 'i2plib'
|
||||||
__description__ = 'A modern asynchronous library for building I2P applications.'
|
__description__ = 'A modern asynchronous library for building I2P applications.'
|
||||||
__url__ = 'https://github.com/l-n-s/i2plib'
|
__url__ = 'https://github.com/l-n-s/i2plib'
|
||||||
__version__ = '0.0.5'
|
__version__ = '0.0.6'
|
||||||
__author__ = 'Viktor Villainov'
|
__author__ = 'Viktor Villainov'
|
||||||
__author_email__ = 'supervillain@riseup.net'
|
__author_email__ = 'supervillain@riseup.net'
|
||||||
__license__ = 'MIT'
|
__license__ = 'MIT'
|
||||||
|
|
169
i2plib/tunnel.py
169
i2plib/tunnel.py
|
@ -8,26 +8,6 @@ import i2plib.utils
|
||||||
|
|
||||||
BUFFER_SIZE = 65536
|
BUFFER_SIZE = 65536
|
||||||
|
|
||||||
class I2PTunnel(object):
|
|
||||||
"""I2P Tunnel object
|
|
||||||
|
|
||||||
This object is returned by tunnel creation coroutines
|
|
||||||
|
|
||||||
:param name: Tunnel session name
|
|
||||||
:param session_writer: Session socket writer instance
|
|
||||||
:param future: Tunnel future
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, session_writer, future):
|
|
||||||
self.name = name
|
|
||||||
self.session_writer = session_writer
|
|
||||||
self.future = future
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""Stop the tunnel"""
|
|
||||||
self.session_writer.close()
|
|
||||||
self.future.cancel()
|
|
||||||
|
|
||||||
async def proxy_data(reader, writer):
|
async def proxy_data(reader, writer):
|
||||||
"""Proxy data from reader to writer"""
|
"""Proxy data from reader to writer"""
|
||||||
try:
|
try:
|
||||||
|
@ -45,104 +25,128 @@ async def proxy_data(reader, writer):
|
||||||
pass
|
pass
|
||||||
logging.debug('close connection')
|
logging.debug('close connection')
|
||||||
|
|
||||||
async def client_tunnel(local_address, remote_destination, loop=None,
|
class I2PTunnel(object):
|
||||||
private_key=None, session_name=None,
|
"""Base I2P Tunnel object, not to be used directly
|
||||||
sam_address=i2plib.sam.DEFAULT_ADDRESS):
|
|
||||||
"""Run a client tunnel in the event loop.
|
:param local_address: A local address to use for a tunnel.
|
||||||
|
E.g. ("127.0.0.1", 6668)
|
||||||
|
:param private_key: (optional) Private key to use in this session. Can be
|
||||||
|
a base64 encoded string, i2plib.sam.PrivateKey instance
|
||||||
|
or None. A new key is created when it is None.
|
||||||
|
:param session_name: (optional) Session nick name. A new session nickname is
|
||||||
|
generated if not specified.
|
||||||
|
:param options: (optional) A dict object with i2cp options
|
||||||
|
:param loop: (optional) Event loop instance
|
||||||
|
:param sam_address: (optional) SAM API address
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, local_address, private_key=None, session_name=None,
|
||||||
|
options={}, loop=None, sam_address=i2plib.sam.DEFAULT_ADDRESS):
|
||||||
|
self.local_address = local_address
|
||||||
|
self.private_key = private_key
|
||||||
|
self.session_name = session_name or i2plib.sam.generate_session_id()
|
||||||
|
self.options = options
|
||||||
|
self.loop = loop
|
||||||
|
self.sam_address = sam_address
|
||||||
|
|
||||||
|
async def _pre_run(self):
|
||||||
|
if not self.private_key:
|
||||||
|
self.private_key = await i2plib.new_private_key(
|
||||||
|
sam_address=self.sam_address, loop=self.loop)
|
||||||
|
_, self.session_writer = await i2plib.aiosam.create_session(
|
||||||
|
self.session_name, style=self.style, options=self.options,
|
||||||
|
sam_address=self.sam_address,
|
||||||
|
loop=self.loop, private_key=self.private_key)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Stop the tunnel"""
|
||||||
|
self.session_writer.close()
|
||||||
|
self.future.cancel()
|
||||||
|
|
||||||
|
class ClientTunnel(I2PTunnel):
|
||||||
|
"""Client tunnel, a subclass of i2plib.tunnel.I2PTunnel
|
||||||
|
|
||||||
If you run a client tunnel with a local address ("127.0.0.1", 6668) and
|
If you run a client tunnel with a local address ("127.0.0.1", 6668) and
|
||||||
a remote destination "irc.echelon.i2p", all connections to 127.0.0.1:6668
|
a remote destination "irc.echelon.i2p", all connections to 127.0.0.1:6668
|
||||||
will be proxied to irc.echelon.i2p.
|
will be proxied to irc.echelon.i2p.
|
||||||
|
|
||||||
:param local_address: A local address to bind a remote destination to. E.g.
|
|
||||||
("127.0.0.1", 6668)
|
|
||||||
:param remote_destination: Remote I2P destination, can be either .i2p
|
:param remote_destination: Remote I2P destination, can be either .i2p
|
||||||
domain, .b32.i2p address, base64 destination or
|
domain, .b32.i2p address, base64 destination or
|
||||||
i2plib.Destination instance
|
i2plib.Destination instance
|
||||||
:param session_name: (optional) Session nick name. A new session nickname is
|
|
||||||
generated if not specified.
|
|
||||||
:param private_key: (optional) Private key to use in this session. Can be
|
|
||||||
a base64 encoded string, i2plib.sam.PrivateKey instance
|
|
||||||
or None. TRANSIENT destination is used when it is None.
|
|
||||||
:param sam_address: (optional) SAM API address
|
|
||||||
:param loop: (optional) Event loop instance
|
|
||||||
:return: an instance of i2plib.tunnel.I2PTunnel
|
|
||||||
"""
|
"""
|
||||||
session_name = session_name or i2plib.sam.generate_session_id()
|
|
||||||
reader, writer = await i2plib.aiosam.create_session(session_name,
|
def __init__(self, remote_destination, *args, **kwargs):
|
||||||
style="STREAM", sam_address=sam_address, loop=loop,
|
super().__init__(*args, **kwargs)
|
||||||
private_key=private_key)
|
self.style = "STREAM"
|
||||||
|
self.remote_destination = remote_destination
|
||||||
|
|
||||||
|
async def run(self):
|
||||||
|
"""A coroutine used to run the tunnel"""
|
||||||
|
await self._pre_run()
|
||||||
|
|
||||||
async def handle_client(client_reader, client_writer):
|
async def handle_client(client_reader, client_writer):
|
||||||
"""Handle local client connection"""
|
"""Handle local client connection"""
|
||||||
remote_reader, remote_writer = await i2plib.aiosam.stream_connect(
|
remote_reader, remote_writer = await i2plib.aiosam.stream_connect(
|
||||||
session_name, remote_destination, sam_address=sam_address,
|
self.session_name, self.remote_destination,
|
||||||
loop=loop)
|
sam_address=self.sam_address, loop=self.loop)
|
||||||
asyncio.ensure_future(proxy_data(remote_reader, client_writer),
|
asyncio.ensure_future(proxy_data(remote_reader, client_writer),
|
||||||
loop=loop)
|
loop=self.loop)
|
||||||
asyncio.ensure_future(proxy_data(client_reader, remote_writer),
|
asyncio.ensure_future(proxy_data(client_reader, remote_writer),
|
||||||
loop=loop)
|
loop=self.loop)
|
||||||
|
|
||||||
tunnel_future = asyncio.ensure_future(
|
self.future = asyncio.ensure_future(
|
||||||
asyncio.start_server(handle_client, *local_address, loop=loop),
|
asyncio.start_server(handle_client, *self.local_address,
|
||||||
loop=loop)
|
loop=self.loop),
|
||||||
return I2PTunnel(session_name, writer, tunnel_future)
|
loop=self.loop)
|
||||||
|
|
||||||
async def server_tunnel(local_address, loop=None, private_key=None,
|
class ServerTunnel(I2PTunnel):
|
||||||
session_name=None, sam_address=i2plib.sam.DEFAULT_ADDRESS):
|
"""Server tunnel, a subclass of i2plib.tunnel.I2PTunnel
|
||||||
"""Run a server tunnel in the event loop.
|
|
||||||
|
|
||||||
If you want to expose a local service 127.0.0.1:80 to the I2P network, run
|
If you want to expose a local service 127.0.0.1:80 to the I2P network, run
|
||||||
a server tunnel with a local address ("127.0.0.1", 80). If you don't
|
a server tunnel with a local address ("127.0.0.1", 80). If you don't
|
||||||
provide a private key or a session name, it will use a TRANSIENT
|
provide a private key or a session name, it will use a TRANSIENT
|
||||||
destination.
|
destination.
|
||||||
|
|
||||||
:param local_address: A local address to bind a remote destination to. E.g.
|
|
||||||
("127.0.0.1", 6668)
|
|
||||||
:param session_name: (optional) Session nick name. A new session nickname is
|
|
||||||
generated if not specified.
|
|
||||||
:param private_key: (optional) Private key to use in this session. Can be
|
|
||||||
a base64 encoded string, i2plib.sam.PrivateKey instance
|
|
||||||
or None. TRANSIENT destination is used when it is None.
|
|
||||||
:param sam_address: (optional) SAM API address
|
|
||||||
:param loop: (optional) Event loop instance
|
|
||||||
:return: an instance of i2plib.tunnel.I2PTunnel
|
|
||||||
"""
|
"""
|
||||||
session_name = session_name or i2plib.sam.generate_session_id()
|
def __init__(self, *args, **kwargs):
|
||||||
reader, writer = await i2plib.aiosam.create_session(session_name,
|
super().__init__(*args, **kwargs)
|
||||||
style="STREAM", sam_address=sam_address, loop=loop,
|
self.style = "STREAM"
|
||||||
private_key=private_key)
|
|
||||||
|
async def run(self):
|
||||||
|
"""A coroutine used to run the tunnel"""
|
||||||
|
await self._pre_run()
|
||||||
|
|
||||||
async def handle_client(incoming, client_reader, client_writer):
|
async def handle_client(incoming, client_reader, client_writer):
|
||||||
# data and dest may come in one chunk
|
# data and dest may come in one chunk
|
||||||
dest, data = incoming.split(b"\n", 1)
|
dest, data = incoming.split(b"\n", 1)
|
||||||
remote_destination = i2plib.sam.Destination(dest.decode())
|
remote_destination = i2plib.sam.Destination(dest.decode())
|
||||||
logging.debug("{} client connected: {}.b32.i2p".format(session_name,
|
logging.debug("{} client connected: {}.b32.i2p".format(
|
||||||
remote_destination.base32))
|
self.session_name, remote_destination.base32))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
remote_reader, remote_writer = await asyncio.wait_for(
|
remote_reader, remote_writer = await asyncio.wait_for(
|
||||||
asyncio.open_connection(
|
asyncio.open_connection(
|
||||||
host=local_address[0], port=local_address[1], loop=loop),
|
host=self.local_address[0],
|
||||||
timeout=5, loop=loop)
|
port=self.local_address[1], loop=self.loop),
|
||||||
|
timeout=5, loop=self.loop)
|
||||||
if data: remote_writer.write(data)
|
if data: remote_writer.write(data)
|
||||||
asyncio.ensure_future(proxy_data(remote_reader, client_writer),
|
asyncio.ensure_future(proxy_data(remote_reader, client_writer),
|
||||||
loop=loop)
|
loop=self.loop)
|
||||||
asyncio.ensure_future(proxy_data(client_reader, remote_writer),
|
asyncio.ensure_future(proxy_data(client_reader, remote_writer),
|
||||||
loop=loop)
|
loop=self.loop)
|
||||||
except ConnectionRefusedError:
|
except ConnectionRefusedError:
|
||||||
client_writer.close()
|
client_writer.close()
|
||||||
|
|
||||||
async def server_loop():
|
async def server_loop():
|
||||||
while True:
|
while True:
|
||||||
client_reader, client_writer = await i2plib.aiosam.stream_accept(
|
client_reader, client_writer = await i2plib.aiosam.stream_accept(
|
||||||
session_name, sam_address=sam_address, loop=loop)
|
self.session_name, sam_address=self.sam_address,
|
||||||
|
loop=self.loop)
|
||||||
incoming = await client_reader.read(BUFFER_SIZE)
|
incoming = await client_reader.read(BUFFER_SIZE)
|
||||||
asyncio.ensure_future(handle_client(
|
asyncio.ensure_future(handle_client(
|
||||||
incoming, client_reader, client_writer), loop=loop)
|
incoming, client_reader, client_writer), loop=self.loop)
|
||||||
|
|
||||||
|
self.future = asyncio.ensure_future(server_loop(), loop=self.loop)
|
||||||
|
|
||||||
tunnel_future = asyncio.ensure_future(server_loop(), loop=loop)
|
|
||||||
return I2PTunnel(session_name, writer, tunnel_future)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
|
@ -171,18 +175,19 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
local_address = i2plib.utils.address_from_string(args.address)
|
local_address = i2plib.utils.address_from_string(args.address)
|
||||||
|
|
||||||
if args.type == 'client':
|
if args.type == "client":
|
||||||
asyncio.ensure_future(client_tunnel(local_address,
|
tunnel = ClientTunnel(args.destination, local_address, loop=loop,
|
||||||
args.destination, loop=loop, private_key=private_key,
|
private_key=private_key, sam_address=SAM_ADDRESS)
|
||||||
sam_address=SAM_ADDRESS), loop=loop)
|
elif args.type == "server":
|
||||||
elif args.type == 'server':
|
tunnel = ServerTunnel(local_address, loop=loop, private_key=private_key,
|
||||||
asyncio.ensure_future(server_tunnel(local_address, loop=loop,
|
sam_address=SAM_ADDRESS)
|
||||||
private_key=private_key, sam_address=SAM_ADDRESS), loop=loop)
|
|
||||||
|
asyncio.ensure_future(tunnel.run(), loop=loop)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
tunnel.stop()
|
||||||
finally:
|
finally:
|
||||||
loop.stop()
|
loop.stop()
|
||||||
loop.close()
|
loop.close()
|
||||||
|
|
Loading…
Reference in New Issue