Cicada Documentation¶
Cicada is a resilient communication framework with peer-to-peer routing.
Features:
- Lower bandwidth requirements for service providers
- Highly-efficient and resilient routing between users
- Safe & secure encryption among trusted peers
- Improved user performance
Installation¶
There are a few minor dependencies that are easily pip
able; the biggest
requirement is that pygame
is used in the visualization tools:
$ pip install -r requirements.txt
If you want to build the documentation as well, install the
full_requirements.txt
, which contains all of the Sphinx dependencies.
There are multiple ways of interacting with the Cicada library:
- The
cicada.py
script is a command-line interface for both creating a swarm and joining an existing swarm. You define a runtime configuration that executes commands in sequence. See the Runtime Interpreter documentation for details. - The
visualizer.py
script is a visualizer that lets you arbitrarily connect a swarm of peers, watch them exchange messages, and stabilize. See the Visualization section for controls. - The
samples/
directory holds a handful of applications for the library, one of which is a single-room chatting app.
Unfortunately, the library is currently only available on Linux (and possible OS X) because of the dependencies I use for NAT traversal (specifically, pynetinfo). I’ll be looking into a cross-platform solution soon.
Using Cicada in Your Application¶
Cicada comes with sample applications, but its up to you to use the library to create a peer-to-peer application of your own. This could be a large variety of decentralized applications, such as secure chat communcation, file-sharing, or efficient mesh networking.
Advanced Features¶
This section outlines advanced features that are or will be available in Cicada in the official 1.0 release.
Attacker Resilience¶
Traditionally, if a peer were to communicate with another peer, the traffic would take a single route through the network topology to get to the other peer. If there is a malicious agent in the network, they could rewrite unencrypted traffic and inject arbitrary payloads. To work around this, traffic can be forked and sent through multiple peers throughout the network simultaneously. This increases overall load on the network, naturally, but is a small price to pay in ensuring that your data isn’t messed with in-transit.
To use this feature, pass the duplicates
keyword argument on a per-message
basis when using the API:
peer = SwarmPeer("localhost", 10000)
peer.connect("10.0.0.1", 50000)
peer.send(("10.0.0.2", 50000), "hello!", duplicates=5)
Visualization¶
When running the Cicada visualization tool, visualizer.py
, there are a
number of controls for manipulating the behavior of the peers:
- Press R to join all peers together into a single network at random.
- Click a peer, press J, then click another peer in order to join the former to the latter.
- Pressing L between peers performs a lookup on the network on the latter’s ID.
- Select a peer and press F to dump the peer’s finger table.
- Select a peer and press P to dump its full list of known peers.
- Select a peer and press B to send a broadcast packet to the entire network that peer is connected to.
Feature Work¶
There is still a long way to go before Cicada has a robust enough feature set for general consumption; this section outlines future plans.
Port Forwarding¶
Most people use devices on personal networks, and are thus hidden behind a
router that is doing network address translation (NAT). Similar to
how BitTorrent needs to temporarily open ports in order to seed content, we need
to do likewise in order to facilitate new peers into the swarm through a local
peer. To do this, we use similar techniques to libtorrent, namely NatPMP and UPnP. These will allow you to create a swarm
peer without worrying about whether or not it will be able to be accessed from
the Internet.
Estimated Release: 0.3.0-alpha
Security & Encryption¶
In a peer-to-peer network, it’s impossible to determine what peers your traffic will travel through on the way to its destination. Standard routing through the Internet faces these same implications, but we implicitly trust that network topology more (we must, in fact, in order to gain any semblance of security).
The only way to ensure secure communications that are immune to Man-in-the-Middle attacks and packet sniffing is to establish a trusted set of encryption keys before using the network. This can be via secure email, and encrypted telephone call, exchanging symmetric keys in person, etc. Once these keys are exchanged, Cicada can use them directly to encrypt all outgoing communication to a particular peer.
If you trust the network (or at least the majority of it – see the
Attacker Resilience section), you can use standard public-key
authentication methods to establish an SSL communcation stream between
particular peers. That is to say, the traffic is still routed through the other
peers, but is encrypted with SSL.
Estimated Release: 1.0.0-rc
If you want to hard-code secret keys, configure a key file like so (choosing one
of either "peer"
, specifying the exact peer ID, or "address"
, specifying
the host:port
pair of the peer):
{
"trusted_hosts": [{
"peer": "24355304810235874286134060455083535315455785472150272366747243996307578662525",
"address": "75.23.66.101:7000",
"outbound_key": "outbound_encryption_key",
"inbound_key": " inbound_encryption_key"
}, {
}]
}
Then, just pass it to the command-line. Any communications between the localhost
and the peer at 75.23.66.101:7000
will be encrypted if the other peer is also
aware of the encryption keys:
$ ./cicada.py -p 7001 --join 75.23.66.101:7000 --keys keylist.json
API Documentation¶
Here we outline detailed usage of the Cicada API; it explains much more than
the technical details under, say, help(swarmlib.swarmnode)
. For examples and tutorials, see the Tutorials page.
Interacting with Cicada: SwarmPeer
¶
This object is the main way of interacting with the Cicada API as it wraps the
lower-level DHT and routing details. It mimics the official socket.socket
interface as closely as possible.
Unlike that interface, though, a bind
is required, since every peer in the
swarm acts like a server for all the others.
-
class
swarmlib.
SwarmPeer
([hooks={}])¶ This creates an object, registering a selection of callbacks that hook into various lower-level functionality. The valid keys into
hooks
are:"send"
: called for every sent packet. it’s called with the following signature:send(PeerSocket, bytes)
, where thePeerSocket
parameter is responsible for sending the data. this includes all messages, including the ones that occur at a lower level, such as the DHT layer."recv"
: called for every time a full high-level data packet is received. it’s called with the following signature:recv(RemoteNode, bytes)
, where theRemoteNode
parameter is the node that the full data packet was received from."new_peer"
: called for every time a newSwarmPeer
joins the swarm:new_peer(PeerSocket)
.
-
SwarmPeer.
bind
(addr, port[, external_ip=None, external_port=None])¶ Binds to a particular address, establishing the listener for this member of a Cicada swarm. A subsequent
connect()
indicates a peer joining an existing swarm, whereas a lack there-of indicates a peer establishing itself as the first member of a swarm. The optional parameters (which must both be specified) set a custom ID for the peer. This is the first method that must be called on aSwarmPeer
instance, before any packet operations.Parameters: - addr (str) – either the IP address of a local interface (such as
eth0
), a hostname likelocalhost
, or an empty string, which would indicate a binding on all interfaces. - port (int) – the port to bind on, in the range [1025, 65535)
- external_ip (int or None) – the external IP address of your host on the network. this applies to NAT traversal situations as seen in the example below where you don’t immediately have access to your external network or a port forwarded on your router. see the
traversal
module for details. - external_port – as with the IP, this is the mapped external port.
from cicada import swarmlib, traversal peer = swarmlib.SwarmPeer() with traversal.PortMapping(5000) as pm: eip = pm.mapper.external_ip peer.bind(pm.local_address, pm.port, eip, pm.eport)
- addr (str) – either the IP address of a local interface (such as
-
SwarmPeer.
connect
(network_host, network_port[, timeout=10])¶ Connects to a peer in an existing swarm.
Parameters: - network_host (str) – the IP address or FQDN of an existing Cicada swarm.
- network_port (int) – similarly, the port of the listening peer
- timeout (int) – after this amount of time (in seconds), the call will immediately return.
-
SwarmPeer.
broadcast
(data[, visited=[]])¶ Broadcasts data to the entire swarm. For details on the broadcasting algorithm, you can read this blog post.
Parameters: - data (bytes) – the raw data to sendtarget (tuple) – one of the following: a 2-tuple (hostname, port); a Hash; or another SwarmPeer instance
- visited (list) – this parameter is largely used internally to the
SwarmPeer
object to perform efficient broadcasting, but can be otherwise specified by the caller in order to indicate the specific peers that should be excluded from the broadcast. the list should containHash
objects.
-
SwarmPeer.
send
(target, data[, duplicates=0])¶ Sends a data packet into the Cicada network.
Parameters: - target (tuple) – one of the following: a 2-tuple (hostname, port); a
Hash
; or anotherSwarmPeer
instance - data (bytes) – the raw data to pack and send
- duplicates (int) – the amount of extra peers to route the message through; this is related to attacker resilience.
- target (tuple) – one of the following: a 2-tuple (hostname, port); a
-
SwarmPeer.
recv
()¶ Blocks until a data message is received from the Cicada network.
Return type: ( SwarmPeer
, bytes, bool)Returns: the source peer that the message came from, the data message we received, and whether or not there are more messages pending
Developer Note
This actually returns RemoteNode
instance
rather than a SwarmPeer
, currently, because I haven’t
finished implementing that yet.
NAT Traversal Methods¶
See the NAT Traversal tutorial for examples.
-
class
traversal.
PortMapping
(port[, protocol="tcp"])¶ Establishes an external port mapping using the NAT traversal methods: UPnP, then NAT-PMP. It’s intended to be used using Python’s
with
construct. See this example for a use-case.If you wish to use one of the port mapping modules specifically, see the documentation for the
UPnP
orNatPMP
objects.Parameters: port (int) – this is the requested port to perform an external mapping on. if the port is already mapped, the with
clause will exit immediately; see theeport
attribute for the resulting port mapping.
-
PortMapping.
eport
¶ Specifies the external port that the mapping succeeded on; this may or may not be the initial port that was passed in.
Low-Level Interaction¶
Routing¶
These objects are used in various places to coordinate routing in the Cicada network, such as specifying a send target (instead of a raw address tuple).
-
class
chordlib.routing.
Hash
([value="", hashed=""])¶ Either you know the initial value and the hash is computed, or you know the hashed value (and its initial value is – by definition – not determinable) and only that is stored.
Custom Swarm Creation¶
Maintainer’s Note
The documentation in this area is much less frequently maintained, as its not intended for consumption. It’s merely a starting point for anyone that isn’t really interested in Cicada and more interested in creating their own DHTs.
This section outlines methods for creating custom swarms by interacting directly with the raw distributed hash table (DHT) objects. All of the objects outlined here cannot join or otherwise interact with a Cicada swarm, unless they understand the higher-level protocol’s expectations.
-
class
chordlib.localnode.
LocalNode
(data, bind_addr[, hooks={}])¶ Creates an unconnected peer in a Chord DHT.
Runtime Interpreter¶
By passing a file to cicada.py
, you can execute a series of commands that
interact with the Cicada API.
Each line is executed in order, and has a set of required parameters. Comment
lines start with a #
and are ignored.
SEND [host] [port] [data...]
Sends a message to a particular address.[port]
must be convertible to an integer.RECV [count]
Waits for a message to be received from anyone. The[count]
parameter is optional, and indicates the number of messages to wait for.BCAST [data...]
Sends the specified data to the entire swarm.OPT key1=value key2=value [key=value...]
Processes and sets the configurable options. All subsequent lines will have these options applied to them.WAIT [time (s)]
Waits for an incoming connection for a certain amount of time. If set to -1, waits indefinitely. This is useful for the first peer in a swarm.
Configurable Options¶
Parameter | Type | Description |
---|---|---|
duplicates | int |
Configures the number of extra paths to take for all of the SEND calls. |
Example Runtimes¶
You usually will want a single “server” runtime that starts the swarm; it waits for the other peers to join. This can look something like this:
WAIT -1
OPT duplicates=3
BCAST Hey everyone, I'm the original peer.
SEND localhost 49611 Hey there, specific client @ localhost:49611, it's me.
RECV 2
You would run this like so:
./cicada.py --interface localhost -p 49610 --no-port-mapping first.conf
Similarly, you’d want a “join immediately” peers that look something like this:
RECV 1
BCAST Hey everyone, I'm a new peer!
RECV 1
SEND localhost 49610 Hey there, localhost:49610; it's me.
This would be run like so:
./cicada.py --interface localhost -p 49611 --no-port-mapping --join localhost:49611 others.conf
Tutorials¶
Jump to a Tutorial
NAT Traversal¶
This is used for peers behind a router, as is the case for most users. There are two methods of external port mapping, and both are covered here. To see usage of “catch-all” port mapping, just reference the example in the API docs.
Note
This example should not be necessary in a few iterations of the library. It’s necessary now because detecting whether or not the peer is behind a router (and thus needs NAT traversal) is not implemented. Eventually, this part will happen automatically.
UPnP¶
Universal Plug and Play is the first method used when
using the generic traversal.PortMapping
object.
The following example tries to map the local port 7777 to external port 8888, increasing the mapped port up to 5 times on failures.
import sys
from cicada.traversal import upnp
LOCAL_PORT = 7777
ATTEMPTS = 5
mapper = upnp.UPnP()
mapper.create()
eport = 8888
for i in xrange(ATTEMPTS):
if mapper.add_port_mapping(LOCAL_PORT, eport, protocol="udp"):
break
if i == ATTEMPTS - 1: continue
print "Failed to map %d <--> %d, trying %d." % (
LOCAL_PORT, eport, eport + 1)
eport += 1
else:
print "Failed to map %d after 5 attempts." % LOCAL_PORT
sys.exit(1)
print "Succeeded in mapping %d <--> %d." % (LOCAL_PORT, eport)
mapper.delete_port_mapping(LOCAL_PORT, protocol="udp")
mapper.cleanup() # removes *all* mappings
Simple 2-Node Echo Server¶
In this sample, we’ll create a 2-peer Cicada swarm and echo messages back and forth between the peers.
""" Establishes a *local* swarm, echoing a message between them.
"""
import sys, time
from cicada import swarmlib
from cicada.traversal import portmapper
local_address = portmapper.PortMapper.get_local_address()
first, second = swarmlib.SwarmPeer(), swarmlib.SwarmPeer()
first.bind(local_address, 5000)
second.bind(local_address, 5001)
second.connect(*first.listener)
first.send(second.listener, "hello!")
src, data, _ = second.recv()
assert data == "hello!"
assert src.chord_addr == first.listener
second.send(first.listener, data[::-1])
src, data, _ = first.recv()
assert data == "!olleh"
assert src.chord_addr == second.listener