Commit 5240f60b authored by Nicolas Wavrant's avatar Nicolas Wavrant

defines interfaces for re6st components

parent f4427cf4
Pipeline #306 skipped
import json, logging, os, sqlite3, socket, subprocess, sys, time, zlib
from .registry import RegistryClient
from . import utils, version, x509
import zope.interface
from interface.cache import ICache
class Cache(object):
zope.interface.implements(ICache)
crl = ()
def __init__(self, db_path, registry, cert, db_size=200):
......
......@@ -385,7 +385,7 @@ def main():
for cmd in config.daemon or ():
cleanup.insert(-1, utils.Popen(cmd, shell=True).stop)
try:
cleanup[-1:-1] = (tunnel_manager.delInterfaces,
cleanup[-1:-1] = (tunnel_manager.delInterface,
tunnel_manager.killAll)
except AttributeError:
pass
......
import logging, socket, struct
from collections import namedtuple
from . import utils
import zope.interface
from interface.routing_protocol import IRoutingProtocol
uint16 = struct.Struct("!H")
header = struct.Struct("!HI")
......@@ -184,6 +186,8 @@ class ConnectionClosed(BabelException):
class Babel(object):
zope.interface.implements(IRoutingProtocol)
_decode = None
def __init__(self, socket_path, handler, network):
......
from zope.interface import Attribute, Interface
class IBaseTunnelManager(Interface):
"""BaseTunnelManager interface specification
Basic Tunnel Manager running a limited set of the re6st network functions.
A BaseTunnelManager will only spawn a re6st process, used to communicate
with other nodes of their network by opening tunnels. It won't accept
requests of other nodes to open a tunnel and act as a server.
The tunnel manager keeps a list of callback functions to run at a given date
"""
def select(r, w, t):
"""
Similar to the select system call
r -- list of files to wait until ready for reading
w -- list of files to wait until ready for writing
t -- timeout list
"""
def selectTimeout(next, callback, force=None):
"""
Updates the next date on which the callback function has to be run, if
it is sooner that the previous defined date
next -- date when to run the callback function. If None, deletes the
callback from the list of callbacks
callback -- function to call on timeout
"""
def newVersion():
"""
Apply new network parameters when new ones have been received from
the registry.
Here is a good place to clean revocated nodes
"""
def invalidatePeer():
"""
Removes known peers whose certificates have expired
"""
def handleServerEvent(sock):
"""
Handler for the communication with the process used as the server-side
connection with a remote node
sock -- socket to read from
"""
def handlePeerEvent():
"""
Handler for the messages received by peers wanting to communicate
directly with the re6st process of this node.
This function has to be appended in the watch-list for reading by the
select event
The peers communicate together by codes. Each code refers to an action:
-0: receives peer's version. Updates own's if it is late
-1: asks for peer's address
-2: softLocks connection to this peer
-3: totally close connection with this peer
-4: asks for peer's version
-5: returns the list of connected peers
"""
def sendto(prefix, msg):
"""
Sends a message to a re6st node, using IPv6.
The destination port is the one on which re6st is listening.
Typically, messages sent with this function will be handle by
the distant "handlePeerEvent" function
prefix -- prefix of peer
msg -- message to send
"""
import zope.interface
class ICache(zope.interface.Interface):
"""Cache interface specification
Saves information to speed up re6st service restart.
Also provides useful information to the re6st process when running
"""
def connecting(prefix, connecting):
"""
Updates number of tries to a peer
prefix -- prefix of peer
connecting -- number of tries of connection to this peer
"""
def resetConnecting():
"""
Resets cache
"""
def addPeer(prefix, address):
"""
Adds or updates peer in cache
prefix -- prefix of peer to add
address -- public address of the peer
"""
def getAddress(prefix):
"""
Returns public ip associated to prefix
prefix -- prefix of a peer
"""
def getPeerList(failed=None):
"""
Returns all peers with their public address from cache
failed -- value to return in case of failure
"""
def getPeerCount(failed=None):
"""
Returns number of peers present in the cache
failed -- value to return in case of failure
"""
def cacheMinimize(size):
"""
Minimizes cache to a limited amount of tested peers
size -- number of peers to keep
"""
def getBootstrapPeer():
"""
Asks a bootstrap list of peers to registry to help bootstraping
"""
def updateConfig():
"""
Gets new network parameters from registry
And update config accordingly
"""
from zope.interface import Attribute, Interface
class IIdentity(Interface):
"""Indentity interface specification
Each node should be authentified in order to allow secure communication.
IIdentity sets functions which allow to verify the identity and
the appartenance to the network of other nodes,
and exchange with them crypted messages
"""
prefix = Attribute("This node's prefix")
network = Attribute("This node's network address")
def maybeRenew(registry, crl):
"""
Returns the next date when the membership to the network should be renewed.
It can be because our membership expired, or CA's certificate did
"""
def loadVerify(cert, strict=None, type=None):
"""
OpenSSL certificate verification. Returns the string representation of
the certificate in case of success
cert -- certificate to verify
strict -- returns verification result, even if certificate is correct
type -- encoding of certificate
"""
def verify(sign, data):
"""
Verifies emitter's authenticity of a string
sign -- signature of the emitter
data -- string to verify
"""
def sign(data):
"""
Signs a string with the node's own certificate
data -- string to sign
"""
def encrypt(cert, data):
"""
Encrypts a string to send to a node
cert -- certificate or key used to crypt communication with this node
data -- data to encrypt
"""
def decrypt(data):
"""
Deciphers data using the node's own key certificate
data -- crypted string to read
"""
def verifyVersion(version):
"""
Verifies network version. Raises VerifyError if it doesn't match
version -- version to check
"""
from zope.interface import Attribute, Interface
class IPeer(Interface):
"""Peer interface specification
IPeer allows the implementation of a protocol to let 2 nodes communicating
together. It includes the primary handshake previous to any exchange, and
the management of a session
"""
prefix = Attribute("prefix of the distant node to which it is connected")
def newSession(key):
"""
Initializes the class when a new session exchange starts
"""
def isConnected():
"""
Returns a boolean indicating if a communication has already been set up
with the distant peeer, and is still opened
"""
def hello0(cert):
"""
Forges a hello0 message
"""
def hello(cert):
"""
Forges a hello message
"""
def hello0Sent():
"""
Marks than a hello0 message has been sent
"""
def sent():
"""
Acknowledges the instance that a message has been sent
"""
def encode(msg, pack):
"""
Encapsulates a message to be sent
msg -- message to send
pack -- structure format of header
"""
def decode(msg, unpack):
"""
Decapsulates data from a network message
msg -- message
unpack -- unpacking structure format of message header
"""
import zope.interface
class IRegistryBackend(zope.interface.Interface):
"""RegistryBackend interface specification
Represents the registry intelligence, which is in charge of managing the network.
Its main tasks are : registering nodes, attributing them IP(s), revoking
nodes, and keeping the network updated and consistent.
"""
network_config = zope.interface.Attribute( \
"dictionary containing the network configuration")
def getConfig(name, *default):
"""
Returns value of config parameter
name -- name of the parameter whose value we want
default -- value to return in case of failure
"""
def setConfig(name_value):
"""
Creates or updates parameter in config
name_value -- tuple containing the parameter and its value
"""
def updateNetworkConfig(_it0=None):
"""
Updates network_config when registry configuration has changed
"""
def encodeVersion(version):
"""
Encodes the version, and returns it.
The returned value should always be greater than the old one
version -- version number
"""
def decodeVersion(version):
"""
Reads the version from its encoded format.
version -- version number
"""
def getCert(client_prefix):
"""
Returns certificate of a node
client_prefix -- prefix of the node
"""
def newPrefix(prefix_len):
"""
Returns a free prefix
prefix_len -- lenghth of the prefix
"""
def getSubjectSerial(self):
"""
Returns smallest unused serial
"""
def createCertificate(client_prefix, subject, pubkey, not_after=None):
"""
Creates a certificate for a new node
client_prefix -- prefix of the node
subject -- subject of the certificate (= network id)
pubkey -- public key of registry
"""
import zope.interface
class IRegistryDispatcher(zope.interface.Interface):
"""RegistryDispatcher interface specification
The registry dispatcher is the door to the network services provided by
the registry.
It should spawn and manage the network facilities in use to communicate
with the nodes (answering queries, sending orders, gathering
information, ...). When sending answers, the dispatcher could just send it
to the closest node, which will do the routing. This node is likely to run
on the same physical machine.
"""
def handle_request(request, method, kw):
"""
This function is a dispatcher of HTTP requests. It is called by
the handler of the BaseHTTPServer spawned by the re6st service
request -- HTTP request (BaseHTTPRequestHandler)
method -- action requested by node. The method called should just
returns the body of the response, and not answer by itself
kw -- dict containing the parameters extracted from the HTTP query
"""
def select(r, w, t):
"""
Similar to the select system call
r -- list of files to wait until ready for reading
w -- list of files to wait until ready for writing
t -- timeout list
"""
def sendto(prefix, code):
"""
Send a message to a re6st node, containing a code. This is used to
send instructions directly to the re6st process of client nodes
prefix -- prefix of the client node
code -- code to send
"""
def recv(code):
"""
Reads message from socket and returns its
content if the code is the one expected
code -- code identifying the message type
Returns : (prefix, msg) or None
with prefix identifying the emitter
"""
from zope.interface import Interface
class IRoutingProtocol(Interface):
"""Routing Protocol interface specification
The under-layer routing protocol in use in re6st should be able
to respond to these requests
"""
def reset():
"""
Resets the routing protocol service
"""
def request_dump():
"""
Requests a dump from the routing protocol
"""
def handle_dump():
"""
Handler for the reponse to the dump request
"""
def send(packet):
"""
Sends a packet via the routing protocol
"""
def select(r, w, t):
"""
Similar to the select system call
r -- list of files to wait until ready for reading
w -- list of files to wait until ready for writing
t -- timeout list
"""
import zope.interface
class IRPCAdministration(zope.interface.Interface):
"""RPCRegistryServer interface specification
RPC Specifications associated to the registry server. It provides
procedures that are in use for maintenance activities, and should be
protected in consequence.
"""
def revoke(cn_or_serial):
"""
Revokes a node's certificate.
cn_or_serial -- prefix of the node, or its certificate's serial
"""
def versions():
"""
Returns a dict with nodes prefix as keys,
associated to their version
"""
def topology():
"""
Returns a graph of the network topology as a json
"""
import zope.interface
class IRPCManager(zope.interface.Interface):
"""RPCRegistryServer interface specification
Implementation to query the RPC registry server. This interface should be
implemented both by the RPC server and the clients.
This interface is an exhaustive list of the requests that the server should
be able to answer to nodes. The method parameters are the arguments expected
by the remote registry procedures.
On the registry side, a method named "handle_request" should be provided,
used as a dispatcher for the called methods.
"""
def hello(client_prefix):
"""
Starts the handshake (hello) between a node and the registry
"""
def requestToken(email):
"""
Generates a token and send it to the email address. This token is
necessary to authentify the client for its first certificate request
email -- email address to which the token will be sent
"""
def requestCertificate(token, req):
"""
Asks the registry for a certificate associated to the token.
If the case of anonymous users able to register is accepted,
the token parameter can be None
token -- token received to the registered email address
req -- certificate request to be validated by the CA authority
"""
def renewCertificate(cn):
"""
Renews the certificate of a node
cn -- prefix of the requesting node
"""
def getCA():
"""
Asks the registry for the CA's certificate
"""
def getBootstrapPeer(cn):
"""
Returns a list of peers for bootstraping a node.
The returned message must be encrypted with this node's certificate
cn -- prefix of the requesting node
"""
def getNetworkConfig(cn):
"""
Asks the registry for the current network configuration
cn -- prefix of the requesting node
"""
from zope.interface import Attribute, Interface
class IConnection(Interface):
"""Tunnel interface specification
Provides basic operations to manage physical tunnels
of any kind between 2 nodes of the network
"""
def open():
"""
Spawns a tunnel connection to a node
"""
def close():
"""
Turns off the tunnel connection
"""
def connected(serial):
"""
Updates cache
"""
def refresh():
"""
Tries to restart connection if it is down
"""
from zope.interface import Attribute, Interface
class ITunnelKiller(Interface):
"""Tunnel interface specification
Closing a tunnel should follow this interface.
A TunnelKiller has to be able to close connections properly, which means
telling the remote end-point that the tunnel should be closed, and
aborting the operation if the remote cannot.
Classes implementing this interface should work like a state machine,
and implement these states, which should be callable :
- softLocking : the tunnel is marked as expensive from a routing point of view
- hardLocking : the tunnel is marked as "cannot be used anymore"
- locked : a request to close the tunnel has been sent to remote end-point
- unlocking : the tunnel cannot be deleted
"""
state = Attribute("current closing state of the tunnel")
def softLocking():
"""
Soft-locks the tunnel, and goes to "hardLocking" state
"""
def hardLocking():
"""
Hard-locks the tunnel, and goes to "locked" state
"""
def unlocking():
"""
Does nothing
"""
def locked():
"""
Does nothing
"""
def unlock():
"""
Resets the cost of the tunnel to its previous state
"""
def abort():
"""
Goes to the "unlocking" state
"""
from zope.interface import Attribute, Interface
from base_tunnel_manager import IBaseTunnelManager
class ITunnelManager(IBaseTunnelManager):
"""TunnelManager interface specification
Manager for all the tunnels used or in use by the service. When this
interface is implemented, it means that the node can act as a real re6st
node, creating and deleting tunnels regularly, at the opposite of an
IBaseTunnelManager, which represents a static node.
When this TunnelManager opens a tunnel or a connection to a remote node, it
is considered as a client. When this TunnelManager accepts a distant node to
connect to itself, it is considered as a server.
It also has the duty to create and delete tunnels to neighbours, and manage
the physical layer (creating and deleting virtual interfaces, ...)
TODO: a TunnelManager just creating mapping between Connections (relation to
a neighbour) and Tunnels (physical mean of communication) ?
"""
def handleClientEvent():
"""
Handler for the communication with the process used as the client-side
connection with a remote node
"""
def resetTunnelRefresh():
"""
Resets the timeout before the next call to refresh
"""
def freeInterface(iface):
"""
Mark a network interface as free (not connected to any neighbour)
"""
def delInterface():
"""
Deletes all the network interfaces created by the service
"""
def killAll():
"""
Closes all connections
"""
def refresh():
"""
Checks all tunnels status and refresh them when necessary.
This function is to add to the callback functions list
"""
def babel_dump():
"""
Refreshes tunnels : kill some, abort killing some, and create new ones
"""
def encrypt():
"""
Returns if the tunnels should be encrypted.
This configuration paramater is received from the registry
Returns a boolean
"""
......@@ -29,6 +29,11 @@ from operator import itemgetter
from OpenSSL import crypto
from urllib import splittype, splithost, unquote, urlencode
from . import ctl, tunnel, utils, version, x509
import zope.interface
from interface.registry_backend import IRegistryBackend
from interface.registry_dispatcher import IRegistryDispatcher
from interface.rpc_administration import IRPCAdministration
from interface.rpc_manager import IRPCManager
HMAC_HEADER = "Re6stHMAC"
RENEW_PERIOD = 30 * 86400
......@@ -43,6 +48,9 @@ def rpc(f):
class RegistryServer(object):
zope.interface.implements(IRegistryBackend, IRegistryDispatcher,
IRPCAdministration, IRPCManager)
peers = 0, ()
cert_duration = 365 * 86400
......@@ -419,7 +427,7 @@ class RegistryServer(object):
cert.get_subject(), cert.get_pubkey(), not_after)
@rpc
def getCa(self):
def getCA(self):
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert.ca)
@rpc
......@@ -552,6 +560,8 @@ class RegistryServer(object):
class RegistryClient(object):
zope.interface.implements(IRPCManager)
_hmac = None
user_agent = "re6stnet/" + version.version
......
......@@ -3,6 +3,11 @@ from collections import defaultdict, deque
from bisect import bisect, insort
from OpenSSL import crypto
from . import ctl, plib, utils, version, x509
import zope.interface
from interface.tunnel_connection import IConnection
from interface.tunnel_killer import ITunnelKiller
from interface.base_tunnel_manager import IBaseTunnelManager
from interface.tunnel_manager import ITunnelManager
PORT = 326
......@@ -39,6 +44,8 @@ class MultiGatewayManager(dict):
class Connection(object):
zope.interface.implements(IConnection)
_retry = 0
serial = None
time = float('inf')
......@@ -111,6 +118,8 @@ class Connection(object):
class TunnelKiller(object):
zope.interface.implements(ITunnelKiller)
state = None
def __init__(self, peer, tunnel_manager, client=False):
......@@ -165,6 +174,8 @@ class TunnelKiller(object):
class BaseTunnelManager(object):
zope.interface.implements(IBaseTunnelManager)
# TODO: To minimize downtime when network parameters change, we should do
# our best to not restart any process. Ideally, this list should be
# empty and the affected subprocesses reloaded.
......@@ -198,7 +209,7 @@ class BaseTunnelManager(object):
p = x509.Peer(self._prefix)
p.stop_date = cache.next_renew
self._peers = [p]
self._timeouts = [(p.stop_date, self.invalidatePeers)]
self._timeouts = [(p.stop_date, self.invalidatePeer)]
def select(self, r, w, t):
r[self.sock] = self.handlePeerEvent
......@@ -218,7 +229,7 @@ class BaseTunnelManager(object):
logging.debug("timeout: adding %r (%s)", callback.__name__, next)
t.append((next, callback))
def invalidatePeers(self):
def invalidatePeer(self):
next = float('inf')
now = time.time()
remove = []
......@@ -231,7 +242,7 @@ class BaseTunnelManager(object):
next = peer.stop_date
for i in reversed(remove):
del self._peers[i]
self.selectTimeout(next, self.invalidatePeers)
self.selectTimeout(next, self.invalidatePeer)
def _getPeer(self, prefix):
return self._peers[bisect(self._peers, prefix) - 1]
......@@ -331,7 +342,7 @@ class BaseTunnelManager(object):
peer.cert = cert
peer.serial = serial
peer.stop_date = stop_date
self.selectTimeout(stop_date, self.invalidatePeers, False)
self.selectTimeout(stop_date, self.invalidatePeer, False)
if seqno:
self._sendto(to, peer.hello(self.cert))
else:
......@@ -479,6 +490,8 @@ class BaseTunnelManager(object):
class TunnelManager(BaseTunnelManager):
zope.interface.implements(ITunnelManager)
NEED_RESTART = BaseTunnelManager.NEED_RESTART.union((
'client_count', 'max_clients', 'tunnel_refresh'))
......@@ -531,7 +544,7 @@ class TunnelManager(BaseTunnelManager):
subprocess.check_call(args)
return iface
def delInterfaces(self):
def delInterface(self):
iface_list = self._free_iface_list
iface_list += self._iface_to_prefix
self._iface_to_prefix.clear()
......
......@@ -4,6 +4,9 @@ from collections import deque
from datetime import datetime
from OpenSSL import crypto
from . import utils
import zope.interface
from interface.identity import IIdentity
from interface.peer import IPeer
def newHmacSecret():
x = datetime.utcnow()
......@@ -86,6 +89,8 @@ class NewSessionError(Exception):
class Cert(object):
zope.interface.implements(IIdentity)
def __init__(self, ca, key, cert=None):
self.ca_path = ca
self.cert_path = cert
......@@ -185,6 +190,9 @@ class Peer(object):
certificates with 4096-bit keys. A weak algorithm is ok as long as there
is no accidental collision. So SHA-1 looks fine.
"""
zope.interface.implements(IPeer)
_hello = _last = 0
_key = newHmacSecret()
serial = None
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment