def __init__(self, Ks, cert): '''Construct a new network object. The application must invoke bind() before using any other function on this object. The private key Ks and certificate cert must be supplied to construct a network object.''' Network.count = Network.count + 1 self.name = 'Network[%d]' % (Network.count) self.queue = multitask.SmartQueue( ) # module's default queue for dispatching and receiving Message events self.qsip = multitask.SmartQueue() # queue for SIP messages self.qstun = multitask.SmartQueue() # queue for STUN-related messages self.Ks, self.cert = Ks, cert self.udp, self.tcp, self.mcast = createSockets(preferred=(ADDRESS, PORT)) self.tcp.listen(5) self.tcpc = dict( ) # table of client connections from Node to connected socket if any. ip, port = getlocaladdr(self.udp) ignore, ptcp = getlocaladdr(self.tcp) self.node = Node(ip=ip, port=port, type=socket.SOCK_DGRAM, guid=H(ip + ':' + str(port))) # TODO: construct this using H(Kp) self.nodetcp = Node(ip=ip, port=ptcp, type=socket.SOCK_STREAM, guid=self.node.guid) self.nodemcast = Node(ip=ADDRESS, port=PORT, type=socket.SOCK_DGRAM, guid=self.node.guid) self.gen = self.gentcp = self.genmcast = None
def sendto(self, identity, data, timeout=30): '''Send a single data object to the remote peer in datagram mode.''' values = yield self.get(guid=H(identity)) for value in map(lambda x: x.value, values): try: value = int(value) except: print 'invalid non-integer value=%r' % (value) continue seq = dht._seq = dht._seq + 1 net = self.net request = Message(name='Datagram:Request', src=net.node, dest=value, seq=seq, sock=hasattr(self, 'identity') and self.identity or None, peer=identity, value=str(data)) if value == net.node.guid: yield net.put(request, timeout=timeout) elif self.isSuperNode: yield net.put(Message(name='Route:Request', src=net.node, dest=value, payload=request), timeout=timeout) else: yield net.send(Message(name='Proxy:Request', src=net.node, payload=request), node=self.client.neighbors[0]) raise StopIteration(None)
def get(self, guid, maxvalues=16, Kp=None, timeout=5): '''Invoke the get method on the connected DHT node if this is a client.''' if self.server or not self.neighbors: if _debug: print 'client.get not a client with valid connections' raise StopIteration([]) net = self.net seq = dht._seq = dht._seq + 1 request = Message(name='Get:Request', seq=seq, src=net.node, dest=guid, maxvalues=maxvalues, hash=Kp and H(str(Kp)) or None) yield net.send(Message(name='Proxy:Request', src=net.node, payload=request), node=self.neighbors[0], timeout=5) response = yield net.get(timeout=timeout, criteria=lambda x: x.seq == seq and x.name == 'Get:Response') # wait for response result = [(v.value, k.nonce, v.Kp, k.expires) for k, v in zip( response.get('keyss', [None] * len(response['vals'])), response['vals']) ] if response else [] raise StopIteration( result ) # don't use response.values as it is a built-in method of base class dict of Message.
def start(self, net=None, servers=None): '''Start the p2p node as ordinary node. Create a network object if none.''' if self.net is None: self.net = net or Network( Ks=crypto.generateRSA()[0], cert=None, port=self.port) self.net.start() # convert from serevrs ip:port list to Node list if servers: servers = [ Node(ip=ip, port=port, type=socket.SOCK_DGRAM, guid=H(ip + ':' + str(port))) for ip, port in servers ] if _debug: print 'using servers=', servers self.client = Client(self.net, server=self.server).start(servers) if self.server: if self.router is None: self.router = dht.Router(self.net).start() if self.storage is None: self.storage = dht.Storage(self.net, self.router).start() if not self.router.initialized: self.router.initialized = True if not self._gens: for gen in [self.handler()]: multitask.add(gen) self._gens.append(gen) return self
def close(self): '''Close the bound socket''' if hasattr(self, 'identity'): result = yield self.remove(guid=H(self.identity), value=self.net.node.guid, nonce=self.nonce, expires=self.expires) del self.identity, self.nonce, self.expires raise StopIteration(None)
def bind(self, identity, interval=3600): '''Bind the server socket to the given identity.''' if hasattr(self, 'identity'): raise Exception('socket already bound') self.identity, self.nonce, self.expires = identity, dht.randomNonce( ), time.time() + interval result = yield self.put(guid=H(identity), value=self.net.node.guid, nonce=self.nonce, expires=self.expires) raise StopIteration(result)
def put(self, guid, value, nonce, expires, Ks=None, put=True, timeout=30): '''Forward the put request to the connected DHT node.''' if self.server or not self.neighbors: # this is a server, or doesn't have valid connections if _debug: print 'client.put not a client with valid connections' raise StopIteration(False) net = self.net seq = dht._seq = dht._seq + 1 request = Message(name='Put:Request', date=time.time(), seq=seq, src=net.node, dest=guid, nonce=nonce, expires=expires, put=put, \ value=str(value) if put else None, hash=H(str(value)), Kp=Ks and dht.extractPublicKey(Ks) or None, \ sigma=dht.sign(Ks, H(str(guid) + str(value) + str(nonce) + str(expires))) if Ks else None) yield net.send(Message(name='Proxy:Request', src=net.node, payload=request), node=self.neighbors[0], timeout=5) response = yield net.get(timeout=timeout, criteria=lambda x: x.seq == seq and x.name == 'Put:Response') # wait for response raise StopIteration(response and response.result)
def send(self, msg, node, timeout=None): '''Send some msg to dest node (Node), and if timeout is specified then return a success (True) or failure (False) within that timeout. Otherwise, the function may return immediately.''' try: start = time.time() if node.type == socket.SOCK_DGRAM and timeout is not None: # no ack required for tcp msg['ack'] = True # require a NetworkAck data = dht.int2bin(self.node.guid) + str( msg) # TODO: this assumes guid is same for all transports. if _debug and msg.name[:4] != 'Hash': print self.name, 'sending %d bytes %s=>%s: %r' % ( len(data), self.node.hostport, node.hostport, msg) if node.type == socket.SOCK_DGRAM: self.udp.sendto(data, (node.ip, node.port)) else: if node in self.tcpc: sock = self.tcpc[node] else: sock = socket.socket(type=socket.SOCK_STREAM) sock.setblocking(0) try: if _debug: print 'connecting to %s' % (node.hostport, ) sock.connect((node.ip, node.port)) except (socket.timeout, socket.error): yield multitask.sleep(2.0) ret = select.select((), (sock, ), (), 0) if len(ret[1]) == 0: if _debug: print 'connection timedout to %s' % ( node.hostport, ) raise multitask.Timeout, 'Cannot connect to the destination' self.tcpc[node] = sock multitask.sleep() multitask.add(self.tcphandler(sock, (node.ip, node.port))) data = struct.pack('!H', len(data)) + data # put a length first. sock.send(data) if msg.ack: hash = H( data ) # hash property to associate the ack to the data request. ack = yield self.get( lambda x: x.name == 'Ack:Indication' and x.hash == hash, timeout=(timeout - (time.time() - start))) if _debug: 'received ack %r' % (ack) if ack is None: raise StopIteration(False) # no ack received raise StopIteration(True) except (multitask.Timeout, socket.error): raise StopIteration(False) # timeout in sendto or get
def connect(self, identity, timeout=30): '''Connect to the given identity. It returns a Socket object on success or None on error.''' values = yield self.get(guid=H(identity)) if _debug: print 'connect() found values=%r' % (values) for value in map(lambda x: x.value, values): try: value = int(value) except: print 'invalid non-integer value=%r' % (value) continue sock = Socket(sock=self, peer=(identity, value), server=False) seq = dht._seq = dht._seq + 1 net = self.net request = Message(name='Connect:Request', src=net.node, dest=value, seq=seq, sock=hasattr(self, 'identity') and self.identity or None, peer=identity) if value == net.node.guid: yield net.put(request, timeout=5) elif self.isSuperNode: yield net.put(Message(name='Route:Request', src=net.node, dest=value, payload=request), timeout=5) else: yield net.send(Message(name='Proxy:Request', src=net.node, payload=request), node=self.client.neighbors[0]) response = yield net.get( timeout=timeout, criteria=lambda x: x.seq == seq and x.name == 'Connect:Response') # wait for response if response: raise StopIteration(sock) else: sock.close() raise StopIteration(None)
def udpreceiver(self, maxsize=16386, timeout=None, interval=30): '''A UDP receiver task which also performs network ack.''' while True: data, addr = yield multitask.recvfrom(self.udp, maxsize, timeout=timeout) msg, remote = self.parse(data, addr, self.udp.type) if not msg: continue # ignore invalid messages. TODO: handle non-p2p message if _debug and msg.name[:4] != 'Hash': print self.name, 'udp-received %s=>%s: %r' % ( remote.hostport, self.node.hostport, msg) if 'ack' in msg and msg.name != 'Ack:Indication': # the remote requires an ack. send one. del msg['ack'] # remove the ack ack = dht.Message( name='Ack:Indication', hash=H(data)) # hash of original received packet yield self.send(msg=ack, node=remote) # send the NetworkAck message msg['remote'] = remote # put remote as an attribute in msg before putting on queue. yield self.put( msg ) # put the parsed msg so that other interested party may get it.
def start(self, servers=[]): '''Start the client with the given set of optional servers list.''' if not self._gens: guid = H(ADDRESS + ':' + str(PORT)) try: bs = [ Node(ip=socket.gethostbyname(BOOTSTRAP), port=PORT, type=socket.SOCK_STREAM, guid=guid) ] except: bs = [] self.candidates = [self.net.nodemcast] + servers + bs if _debug: print 'Client.start candidates=', self.candidates self.neighbors = [] self._gens = [ self.discoverhandler(), self.bootstrap(), self.clienthandler() ] # , self.pinghandler() for gen in self._gens: multitask.add(gen) return self