From 61f13ea7f7ef41defc9cb65ad5a7e818fc1501af Mon Sep 17 00:00:00 2001 From: Jeromy Johnson Date: Thu, 31 Jul 2014 17:43:48 -0700 Subject: [PATCH 01/30] begin planning of identification process --- identify/identify.go | 28 ++++++++++++++++ identify/message.proto | 3 ++ routing/dht/dht.go | 74 +++++++++++++++++++++++++++++++++++++----- swarm/swarm.go | 47 +++++++++++++++++++++++---- 4 files changed, 138 insertions(+), 14 deletions(-) create mode 100644 identify/identify.go create mode 100644 identify/message.proto diff --git a/identify/identify.go b/identify/identify.go new file mode 100644 index 000000000..87a8ec60d --- /dev/null +++ b/identify/identify.go @@ -0,0 +1,28 @@ +// The identify package handles how peers identify with eachother upon +// connection to the network +package identify + +import ( + peer "github.com/jbenet/go-ipfs/peer" + swarm "github.com/jbenet/go-ipfs/swarm" +) + +// Perform initial communication with this peer to share node ID's and +// initiate communication +func Handshake(self *peer.Peer, conn *swarm.Conn) error { + + // temporary: + // put your own id in a 16byte buffer and send that over to + // the peer as your ID, then wait for them to send their ID. + // Once that trade is finished, the handshake is complete and + // both sides should 'trust' each other + + id := make([]byte, 16) + copy(id, self.ID) + + conn.Outgoing.MsgChan <- id + resp := <-conn.Incoming.MsgChan + conn.Peer.ID = peer.ID(resp) + + return nil +} diff --git a/identify/message.proto b/identify/message.proto new file mode 100644 index 000000000..71804c883 --- /dev/null +++ b/identify/message.proto @@ -0,0 +1,3 @@ +message Identify { + required bytes id = 1; +} diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 0f55ba45b..62d4a71cf 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -2,10 +2,14 @@ package dht import ( "sync" + "time" peer "github.com/jbenet/go-ipfs/peer" swarm "github.com/jbenet/go-ipfs/swarm" u "github.com/jbenet/go-ipfs/util" + identify "github.com/jbenet/go-ipfs/identify" + + ma "github.com/jbenet/go-multiaddr" ds "github.com/jbenet/datastore.go" @@ -35,15 +39,44 @@ type IpfsDHT struct { shutdown chan struct{} } -func NewDHT(p *peer.Peer) *IpfsDHT { +// Create a new DHT object with the given peer as the 'local' host +func NewDHT(p *peer.Peer) (*IpfsDHT, error) { dht := new(IpfsDHT) - dht.self = p + dht.network = swarm.NewSwarm(p) + //TODO: should Listen return an error? + dht.network.Listen() + + dht.datastore = ds.NewMapDatastore() + + dht.self = p dht.listeners = make(map[uint64]chan *swarm.Message) dht.shutdown = make(chan struct{}) - return dht + return dht, nil } +// Connect to a new peer at the given address +func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) error { + peer := new(peer.Peer) + peer.AddAddress(addr) + + conn,err := swarm.Dial("tcp", peer) + if err != nil { + return err + } + + err = identify.Handshake(dht.self, conn) + if err != nil { + return err + } + + dht.network.StartConn(conn.Peer.Key(), conn) + + // TODO: Add this peer to our routing table + return nil +} + + // Read in all messages from swarm and handle them appropriately // NOTE: this function is just a quick sketch func (dht *IpfsDHT) handleMessages() { @@ -134,11 +167,9 @@ func (dht *IpfsDHT) handlePing(p *peer.Peer, pmes *DHTMessage) { resp := new(DHTMessage) resp.Id = pmes.Id resp.Response = &isResponse + resp.Type = pmes.Type - mes := new(swarm.Message) - mes.Peer = p - mes.Data = []byte(resp.String()) - dht.network.Chan.Outgoing <- mes + dht.network.Chan.Outgoing <-swarm.NewMessage(p, []byte(resp.String())) } @@ -162,9 +193,36 @@ func (dht *IpfsDHT) Unlisten(mesid uint64) { close(ch) } - // Stop all communications from this node and shut down func (dht *IpfsDHT) Halt() { dht.shutdown <- struct{}{} dht.network.Close() } + +// Ping a node, log the time it took +func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) { + // Thoughts: maybe this should accept an ID and do a peer lookup? + id := GenerateMessageID() + mes_type := DHTMessage_PING + pmes := new(DHTMessage) + pmes.Id = &id + pmes.Type = &mes_type + + mes := new(swarm.Message) + mes.Peer = p + mes.Data = []byte(pmes.String()) + + before := time.Now() + response_chan := dht.ListenFor(id) + dht.network.Chan.Outgoing <- mes + + tout := time.After(timeout) + select { + case <-response_chan: + roundtrip := time.Since(before) + u.DOut("Ping took %s.", roundtrip.String()) + case <-tout: + // Timed out, think about removing node from network + u.DOut("Ping node timed out.") + } +} diff --git a/swarm/swarm.go b/swarm/swarm.go index fccc74777..8f6676190 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -2,11 +2,12 @@ package swarm import ( "fmt" + "net" + "sync" + peer "github.com/jbenet/go-ipfs/peer" u "github.com/jbenet/go-ipfs/util" ma "github.com/jbenet/go-multiaddr" - "net" - "sync" ) // Message represents a packet of information sent to or received from a @@ -19,6 +20,14 @@ type Message struct { Data []byte } +// Cleaner looking helper function to make a new message struct +func NewMessage(p *peer.Peer, data []byte) *Message { + return &Message{ + Peer: p, + Data: data, + } +} + // Chan is a swam channel, which provides duplex communication and errors. type Chan struct { Outgoing chan *Message @@ -87,7 +96,8 @@ func (s *Swarm) connListen(maddr *ma.Multiaddr) error { for { nconn, err := list.Accept() if err != nil { - u.PErr("Failed to accept connection: %s - %s", netstr, addr) + u.PErr("Failed to accept connection: %s - %s [%s]", netstr, + addr, err) return } go s.handleNewConn(nconn) @@ -99,7 +109,27 @@ func (s *Swarm) connListen(maddr *ma.Multiaddr) error { // Handle getting ID from this peer and adding it into the map func (s *Swarm) handleNewConn(nconn net.Conn) { - panic("Not yet implemented!") + p := MakePeerFromConn(nconn) + + var addr *ma.Multiaddr + + //naddr := nconn.RemoteAddr() + //addr := ma.FromDialArgs(naddr.Network(), naddr.String()) + + conn := &Conn{ + Peer: p, + Addr: addr, + Conn: nconn, + } + + newConnChans(conn) + go s.fanIn(conn) +} + +// Negotiate with peer for its ID and create a peer object +// TODO: this might belong in the peer package +func MakePeerFromConn(conn net.Conn) *peer.Peer { + panic("Not yet implemented.") } // Close closes a swarm. @@ -140,6 +170,11 @@ func (s *Swarm) Dial(peer *peer.Peer) (*Conn, error) { return nil, err } + s.StartConn(k, conn) + return conn, nil +} + +func (s *Swarm) StartConn(k u.Key, conn *Conn) { // add to conns s.connsLock.Lock() s.conns[k] = conn @@ -147,7 +182,6 @@ func (s *Swarm) Dial(peer *peer.Peer) (*Conn, error) { // kick off reader goroutine go s.fanIn(conn) - return conn, nil } // Handles the unwrapping + sending of messages to the right connection. @@ -165,7 +199,8 @@ func (s *Swarm) fanOut() { conn, found := s.conns[msg.Peer.Key()] s.connsLock.RUnlock() if !found { - e := fmt.Errorf("Sent msg to peer without open conn: %v", msg.Peer) + e := fmt.Errorf("Sent msg to peer without open conn: %v", + msg.Peer) s.Chan.Errors <- e } From 8d98d4b48dd4d47cf7a4c0aca9e9c192db64617a Mon Sep 17 00:00:00 2001 From: Jeromy Date: Thu, 31 Jul 2014 21:55:44 -0700 Subject: [PATCH 02/30] making connections between nodes get closer to working --- identify/identify.go | 14 ++++++-------- routing/dht/dht.go | 32 +++++++++++++++++++++++++------- swarm/swarm.go | 40 +++++++++++++++++++++++----------------- util/util.go | 4 ++-- 4 files changed, 56 insertions(+), 34 deletions(-) diff --git a/identify/identify.go b/identify/identify.go index 87a8ec60d..5d12b3cab 100644 --- a/identify/identify.go +++ b/identify/identify.go @@ -4,12 +4,12 @@ package identify import ( peer "github.com/jbenet/go-ipfs/peer" - swarm "github.com/jbenet/go-ipfs/swarm" + u "github.com/jbenet/go-ipfs/util" ) // Perform initial communication with this peer to share node ID's and // initiate communication -func Handshake(self *peer.Peer, conn *swarm.Conn) error { +func Handshake(self, remote *peer.Peer, in, out chan []byte) error { // temporary: // put your own id in a 16byte buffer and send that over to @@ -17,12 +17,10 @@ func Handshake(self *peer.Peer, conn *swarm.Conn) error { // Once that trade is finished, the handshake is complete and // both sides should 'trust' each other - id := make([]byte, 16) - copy(id, self.ID) - - conn.Outgoing.MsgChan <- id - resp := <-conn.Incoming.MsgChan - conn.Peer.ID = peer.ID(resp) + out <- self.ID + resp := <-in + remote.ID = peer.ID(resp) + u.DOut("Got node id: %s", string(remote.ID)) return nil } diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 62d4a71cf..9d6d1223a 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -55,6 +55,10 @@ func NewDHT(p *peer.Peer) (*IpfsDHT, error) { return dht, nil } +func (dht *IpfsDHT) Start() { + go dht.handleMessages() +} + // Connect to a new peer at the given address func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) error { peer := new(peer.Peer) @@ -65,24 +69,26 @@ func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) error { return err } - err = identify.Handshake(dht.self, conn) + err = identify.Handshake(dht.self, peer, conn.Incoming.MsgChan, conn.Outgoing.MsgChan) if err != nil { return err } - dht.network.StartConn(conn.Peer.Key(), conn) + dht.network.StartConn(conn) // TODO: Add this peer to our routing table return nil } - // Read in all messages from swarm and handle them appropriately // NOTE: this function is just a quick sketch func (dht *IpfsDHT) handleMessages() { + u.DOut("Being message handling routine") for { select { case mes := <-dht.network.Chan.Incoming: + u.DOut("recieved message from swarm.") + pmes := new(DHTMessage) err := proto.Unmarshal(mes.Data, pmes) if err != nil { @@ -118,6 +124,8 @@ func (dht *IpfsDHT) handleMessages() { dht.handleFindNode(mes.Peer, pmes) } + case err := <-dht.network.Chan.Errors: + panic(err) case <-dht.shutdown: return } @@ -158,10 +166,6 @@ func (dht *IpfsDHT) handlePutValue(p *peer.Peer, pmes *DHTMessage) { } } -func (dht *IpfsDHT) handleFindNode(p *peer.Peer, pmes *DHTMessage) { - panic("Not implemented.") -} - func (dht *IpfsDHT) handlePing(p *peer.Peer, pmes *DHTMessage) { isResponse := true resp := new(DHTMessage) @@ -172,6 +176,18 @@ func (dht *IpfsDHT) handlePing(p *peer.Peer, pmes *DHTMessage) { dht.network.Chan.Outgoing <-swarm.NewMessage(p, []byte(resp.String())) } +func (dht *IpfsDHT) handleFindNode(p *peer.Peer, pmes *DHTMessage) { + panic("Not implemented.") +} + +func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *DHTMessage) { + panic("Not implemented.") +} + +func (dht *IpfsDHT) handleAddProvider(p *peer.Peer, pmes *DHTMessage) { + panic("Not implemented.") +} + // Register a handler for a specific message ID, used for getting replies // to certain messages (i.e. response to a GET_VALUE message) @@ -202,6 +218,8 @@ func (dht *IpfsDHT) Halt() { // Ping a node, log the time it took func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) { // Thoughts: maybe this should accept an ID and do a peer lookup? + u.DOut("Enter Ping.") + id := GenerateMessageID() mes_type := DHTMessage_PING pmes := new(DHTMessage) diff --git a/swarm/swarm.go b/swarm/swarm.go index 8f6676190..de238768b 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -8,6 +8,7 @@ import ( peer "github.com/jbenet/go-ipfs/peer" u "github.com/jbenet/go-ipfs/util" ma "github.com/jbenet/go-multiaddr" + ident "github.com/jbenet/go-ipfs/identify" ) // Message represents a packet of information sent to or received from a @@ -109,27 +110,21 @@ func (s *Swarm) connListen(maddr *ma.Multiaddr) error { // Handle getting ID from this peer and adding it into the map func (s *Swarm) handleNewConn(nconn net.Conn) { - p := MakePeerFromConn(nconn) - - var addr *ma.Multiaddr - - //naddr := nconn.RemoteAddr() - //addr := ma.FromDialArgs(naddr.Network(), naddr.String()) + p := new(peer.Peer) conn := &Conn{ Peer: p, - Addr: addr, + Addr: nil, Conn: nconn, } - newConnChans(conn) - go s.fanIn(conn) -} -// Negotiate with peer for its ID and create a peer object -// TODO: this might belong in the peer package -func MakePeerFromConn(conn net.Conn) *peer.Peer { - panic("Not yet implemented.") + err := ident.Handshake(s.local, p, conn.Incoming.MsgChan, conn.Outgoing.MsgChan) + if err != nil { + panic(err) + } + + s.StartConn(conn) } // Close closes a swarm. @@ -170,14 +165,19 @@ func (s *Swarm) Dial(peer *peer.Peer) (*Conn, error) { return nil, err } - s.StartConn(k, conn) + s.StartConn(conn) return conn, nil } -func (s *Swarm) StartConn(k u.Key, conn *Conn) { +func (s *Swarm) StartConn(conn *Conn) { + if conn == nil { + panic("tried to start nil Conn!") + } + + u.DOut("Starting connection: %s", string(conn.Peer.ID)) // add to conns s.connsLock.Lock() - s.conns[k] = conn + s.conns[conn.Peer.Key()] = conn s.connsLock.Unlock() // kick off reader goroutine @@ -191,6 +191,7 @@ func (s *Swarm) fanOut() { case <-s.Chan.Close: return // told to close. case msg, ok := <-s.Chan.Outgoing: + u.DOut("fanOut: outgoing message for: '%s'", msg.Peer.Key()) if !ok { return } @@ -198,14 +199,17 @@ func (s *Swarm) fanOut() { s.connsLock.RLock() conn, found := s.conns[msg.Peer.Key()] s.connsLock.RUnlock() + if !found { e := fmt.Errorf("Sent msg to peer without open conn: %v", msg.Peer) s.Chan.Errors <- e + continue } // queue it in the connection's buffer conn.Outgoing.MsgChan <- msg.Data + u.DOut("fanOut: message off.") } } } @@ -225,6 +229,7 @@ Loop: break Loop case data, ok := <-conn.Incoming.MsgChan: + u.DOut("fanIn: got message from incoming channel.") if !ok { e := fmt.Errorf("Error retrieving from conn: %v", conn) s.Chan.Errors <- e @@ -234,6 +239,7 @@ Loop: // wrap it for consumers. msg := &Message{Peer: conn.Peer, Data: data} s.Chan.Incoming <- msg + u.DOut("fanIn: message off.") } } diff --git a/util/util.go b/util/util.go index 69831ff8d..d54be111e 100644 --- a/util/util.go +++ b/util/util.go @@ -41,12 +41,12 @@ func TildeExpansion(filename string) (string, error) { // PErr is a shorthand printing function to output to Stderr. func PErr(format string, a ...interface{}) { - fmt.Fprintf(os.Stderr, format, a...) + fmt.Fprintf(os.Stderr, format + "\n", a...) } // POut is a shorthand printing function to output to Stdout. func POut(format string, a ...interface{}) { - fmt.Fprintf(os.Stdout, format, a...) + fmt.Fprintf(os.Stdout, format + "\n", a...) } // DErr is a shorthand debug printing function to output to Stderr. From 92fb51d9a2fef50c55d9d674a1813f68f77a6f4f Mon Sep 17 00:00:00 2001 From: Jeromy Johnson Date: Fri, 1 Aug 2014 13:21:51 -0700 Subject: [PATCH 03/30] finish basic communcations between nodes and add a test of the ping operation --- routing/dht/dht.go | 68 ++++++++++++++++++-------------------- routing/dht/dht_test.go | 55 ++++++++++++++++++++++++++++++ routing/dht/pDHTMessage.go | 24 ++++++++++++++ swarm/swarm.go | 42 ++++++++++++++++++++--- 4 files changed, 149 insertions(+), 40 deletions(-) create mode 100644 routing/dht/dht_test.go create mode 100644 routing/dht/pDHTMessage.go diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 9d6d1223a..ae320426c 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -41,20 +41,22 @@ type IpfsDHT struct { // Create a new DHT object with the given peer as the 'local' host func NewDHT(p *peer.Peer) (*IpfsDHT, error) { + network := swarm.NewSwarm(p) + err := network.Listen() + if err != nil { + return nil,err + } + dht := new(IpfsDHT) - - dht.network = swarm.NewSwarm(p) - //TODO: should Listen return an error? - dht.network.Listen() - + dht.network = network dht.datastore = ds.NewMapDatastore() - dht.self = p dht.listeners = make(map[uint64]chan *swarm.Message) dht.shutdown = make(chan struct{}) return dht, nil } +// Start up background goroutines needed by the DHT func (dht *IpfsDHT) Start() { go dht.handleMessages() } @@ -111,6 +113,7 @@ func (dht *IpfsDHT) handleMessages() { } // + u.DOut("Got message type: %d", pmes.GetType()) switch pmes.GetType() { case DHTMessage_GET_VALUE: dht.handleGetValue(mes.Peer, pmes) @@ -121,7 +124,7 @@ func (dht *IpfsDHT) handleMessages() { case DHTMessage_ADD_PROVIDER: case DHTMessage_GET_PROVIDERS: case DHTMessage_PING: - dht.handleFindNode(mes.Peer, pmes) + dht.handlePing(mes.Peer, pmes) } case err := <-dht.network.Chan.Errors: @@ -136,18 +139,15 @@ func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *DHTMessage) { dskey := ds.NewKey(pmes.GetKey()) i_val, err := dht.datastore.Get(dskey) if err == nil { - isResponse := true - resp := new(DHTMessage) - resp.Response = &isResponse - resp.Id = pmes.Id - resp.Key = pmes.Key + resp := &pDHTMessage{ + Response: true, + Id: *pmes.Id, + Key: *pmes.Key, + Value: i_val.([]byte), + } - val := i_val.([]byte) - resp.Value = val - - mes := new(swarm.Message) - mes.Peer = p - mes.Data = []byte(resp.String()) + mes := swarm.NewMessage(p, resp.ToProtobuf()) + dht.network.Chan.Outgoing <- mes } else if err == ds.ErrNotFound { // Find closest node(s) to desired key and reply with that info // TODO: this will need some other metadata in the protobuf message @@ -167,13 +167,13 @@ func (dht *IpfsDHT) handlePutValue(p *peer.Peer, pmes *DHTMessage) { } func (dht *IpfsDHT) handlePing(p *peer.Peer, pmes *DHTMessage) { - isResponse := true - resp := new(DHTMessage) - resp.Id = pmes.Id - resp.Response = &isResponse - resp.Type = pmes.Type + resp := &pDHTMessage{ + Type: pmes.GetType(), + Response: true, + Id: pmes.GetId(), + } - dht.network.Chan.Outgoing <-swarm.NewMessage(p, []byte(resp.String())) + dht.network.Chan.Outgoing <-swarm.NewMessage(p, resp.ToProtobuf()) } func (dht *IpfsDHT) handleFindNode(p *peer.Peer, pmes *DHTMessage) { @@ -199,6 +199,7 @@ func (dht *IpfsDHT) ListenFor(mesid uint64) <-chan *swarm.Message { return lchan } +// Unregister the given message id from the listener map func (dht *IpfsDHT) Unlisten(mesid uint64) { dht.listenLock.Lock() ch, ok := dht.listeners[mesid] @@ -216,31 +217,26 @@ func (dht *IpfsDHT) Halt() { } // Ping a node, log the time it took -func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) { +func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { // Thoughts: maybe this should accept an ID and do a peer lookup? u.DOut("Enter Ping.") - id := GenerateMessageID() - mes_type := DHTMessage_PING - pmes := new(DHTMessage) - pmes.Id = &id - pmes.Type = &mes_type - - mes := new(swarm.Message) - mes.Peer = p - mes.Data = []byte(pmes.String()) + pmes := pDHTMessage{Id: GenerateMessageID(), Type: DHTMessage_PING} + mes := swarm.NewMessage(p, pmes.ToProtobuf()) before := time.Now() - response_chan := dht.ListenFor(id) + response_chan := dht.ListenFor(pmes.Id) dht.network.Chan.Outgoing <- mes tout := time.After(timeout) select { case <-response_chan: roundtrip := time.Since(before) - u.DOut("Ping took %s.", roundtrip.String()) + u.POut("Ping took %s.", roundtrip.String()) + return nil case <-tout: // Timed out, think about removing node from network u.DOut("Ping node timed out.") + return u.ErrTimeout } } diff --git a/routing/dht/dht_test.go b/routing/dht/dht_test.go new file mode 100644 index 000000000..8485a1d83 --- /dev/null +++ b/routing/dht/dht_test.go @@ -0,0 +1,55 @@ +package dht + +import ( + "testing" + peer "github.com/jbenet/go-ipfs/peer" + ma "github.com/jbenet/go-multiaddr" + u "github.com/jbenet/go-ipfs/util" + + "time" +) + +func TestPing(t *testing.T) { + u.Debug = false + addr_a,err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/1234") + if err != nil { + t.Fatal(err) + } + addr_b,err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5678") + if err != nil { + t.Fatal(err) + } + + peer_a := new(peer.Peer) + peer_a.AddAddress(addr_a) + peer_a.ID = peer.ID([]byte("peer_a")) + + peer_b := new(peer.Peer) + peer_b.AddAddress(addr_b) + peer_b.ID = peer.ID([]byte("peer_b")) + + dht_a,err := NewDHT(peer_a) + if err != nil { + t.Fatal(err) + } + + dht_b,err := NewDHT(peer_b) + if err != nil { + t.Fatal(err) + } + + + dht_a.Start() + dht_b.Start() + + err = dht_a.Connect(addr_b) + if err != nil { + t.Fatal(err) + } + + //Test that we can ping the node + err = dht_a.Ping(peer_b, time.Second * 2) + if err != nil { + t.Fatal(err) + } +} diff --git a/routing/dht/pDHTMessage.go b/routing/dht/pDHTMessage.go new file mode 100644 index 000000000..65c03b1f8 --- /dev/null +++ b/routing/dht/pDHTMessage.go @@ -0,0 +1,24 @@ +package dht + +// A helper struct to make working with protbuf types easier +type pDHTMessage struct { + Type DHTMessage_MessageType + Key string + Value []byte + Response bool + Id uint64 +} + +func (m *pDHTMessage) ToProtobuf() *DHTMessage { + pmes := new(DHTMessage) + if m.Value != nil { + pmes.Value = m.Value + } + + pmes.Type = &m.Type + pmes.Key = &m.Key + pmes.Response = &m.Response + pmes.Id = &m.Id + + return pmes +} diff --git a/swarm/swarm.go b/swarm/swarm.go index de238768b..1ac277d07 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -9,6 +9,7 @@ import ( u "github.com/jbenet/go-ipfs/util" ma "github.com/jbenet/go-multiaddr" ident "github.com/jbenet/go-ipfs/identify" + proto "code.google.com/p/goprotobuf/proto" ) // Message represents a packet of information sent to or received from a @@ -22,10 +23,14 @@ type Message struct { } // Cleaner looking helper function to make a new message struct -func NewMessage(p *peer.Peer, data []byte) *Message { +func NewMessage(p *peer.Peer, data proto.Message) *Message { + bytes,err := proto.Marshal(data) + if err != nil { + panic(err) + } return &Message{ Peer: p, - Data: data, + Data: bytes, } } @@ -47,6 +52,25 @@ func NewChan(bufsize int) *Chan { } } +// Contains a set of errors mapping to each of the swarms addresses +// that were listened on +type SwarmListenErr struct { + Errors []error +} + +func (se *SwarmListenErr) Error() string { + if se == nil { + return "" + } + var out string + for i,v := range se.Errors { + if v != nil { + out += fmt.Sprintf("%d: %s\n", i, v) + } + } + return out +} + // Swarm is a connection muxer, allowing connections to other peers to // be opened and closed, while still using the same Chan for all // communication. The Chan sends/receives Messages, which note the @@ -71,13 +95,23 @@ func NewSwarm(local *peer.Peer) *Swarm { } // Open listeners for each network the swarm should listen on -func (s *Swarm) Listen() { - for _, addr := range s.local.Addresses { +func (s *Swarm) Listen() error { + var ret_err *SwarmListenErr + for i, addr := range s.local.Addresses { err := s.connListen(addr) if err != nil { + if ret_err != nil { + ret_err = new(SwarmListenErr) + ret_err.Errors = make([]error, len(s.local.Addresses)) + } + ret_err.Errors[i] = err u.PErr("Failed to listen on: %s [%s]", addr, err) } } + if ret_err == nil { + return nil + } + return ret_err } // Listen for new connections on the given multiaddr From 31dc65b96b681c2565245a7c76d4b8e06e8fd88e Mon Sep 17 00:00:00 2001 From: Jeromy Date: Fri, 1 Aug 2014 18:07:09 -0700 Subject: [PATCH 04/30] clean up after listeners on shutdown --- swarm/swarm.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/swarm/swarm.go b/swarm/swarm.go index 1ac277d07..d6cbcc721 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -81,6 +81,7 @@ type Swarm struct { connsLock sync.RWMutex local *peer.Peer + listeners []net.Listener } // NewSwarm constructs a Swarm, with a Chan. @@ -126,6 +127,9 @@ func (s *Swarm) connListen(maddr *ma.Multiaddr) error { return err } + // NOTE: this may require a lock around it later. currently, only run on setup + s.listeners = append(s.listeners, list) + // Accept and handle new connections on this listener until it errors go func() { for { @@ -172,6 +176,10 @@ func (s *Swarm) Close() { } s.Chan.Close <- true // fan out s.Chan.Close <- true // listener + + for _,list := range s.listeners { + list.Close() + } } // Dial connects to a peer. From 35a4086e066b2602f9c8d3ae28e6ec957e606242 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Sun, 3 Aug 2014 13:37:09 -0700 Subject: [PATCH 05/30] rough kbucket implementation, tests and cleanup to follow --- peer/peer.go | 4 +- routing/dht/routing.go | 4 +- routing/dht/table.go | 170 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 170 insertions(+), 8 deletions(-) diff --git a/peer/peer.go b/peer/peer.go index e7c3af2b4..3af7e27e6 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -12,8 +12,8 @@ import ( type ID mh.Multihash // Utililty function for comparing two peer ID's -func (id *ID) Equal(other *ID) bool { - return bytes.Equal(*id, *other) +func (id ID) Equal(other ID) bool { + return bytes.Equal(id, other) } // Map maps Key (string) : *Peer (slices are not comparable). diff --git a/routing/dht/routing.go b/routing/dht/routing.go index e9ed64d98..66e27e369 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -21,7 +21,7 @@ func GenerateMessageID() uint64 { // PutValue adds value corresponding to given Key. func (s *IpfsDHT) PutValue(key u.Key, value []byte) error { var p *peer.Peer - p = s.routes.NearestNode(key) + p = s.routes.NearestPeer(convertKey(key)) pmes_type := DHTMessage_PUT_VALUE str_key := string(key) @@ -44,7 +44,7 @@ func (s *IpfsDHT) PutValue(key u.Key, value []byte) error { // GetValue searches for the value corresponding to given Key. func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { var p *peer.Peer - p = s.routes.NearestNode(key) + p = s.routes.NearestPeer(convertKey(key)) str_key := string(key) mes_type := DHTMessage_GET_VALUE diff --git a/routing/dht/table.go b/routing/dht/table.go index d7625e462..b8c20f8ab 100644 --- a/routing/dht/table.go +++ b/routing/dht/table.go @@ -3,6 +3,9 @@ package dht import ( "bytes" "container/list" + "sort" + + "crypto/sha256" peer "github.com/jbenet/go-ipfs/peer" u "github.com/jbenet/go-ipfs/util" @@ -16,18 +19,177 @@ import ( type ID []byte // Bucket holds a list of peers. -type Bucket []*list.List +type Bucket list.List + +func (b *Bucket) Find(id peer.ID) *list.Element { + bucket_list := (*list.List)(b) + for e := bucket_list.Front(); e != nil; e = e.Next() { + if e.Value.(*peer.Peer).ID.Equal(id) { + return e + } + } + return nil +} + +func (b *Bucket) MoveToFront(e *list.Element) { + bucket_list := (*list.List)(b) + bucket_list.MoveToFront(e) +} + +func (b *Bucket) PushFront(p *peer.Peer) { + bucket_list := (*list.List)(b) + bucket_list.PushFront(p) +} + +func (b *Bucket) PopBack() *peer.Peer { + bucket_list := (*list.List)(b) + last := bucket_list.Back() + bucket_list.Remove(last) + return last.Value.(*peer.Peer) +} + +func (b *Bucket) Len() int { + bucket_list := (*list.List)(b) + return bucket_list.Len() +} + +func (b *Bucket) Split(cpl int, target ID) *Bucket { + bucket_list := (*list.List)(b) + out := list.New() + e := bucket_list.Front() + for e != nil { + peer_id := convertPeerID(e.Value.(*peer.Peer).ID) + peer_cpl := xor(peer_id, target).commonPrefixLen() + if peer_cpl > cpl { + cur := e + out.PushBack(e.Value) + e = e.Next() + bucket_list.Remove(cur) + continue + } + } + return (*Bucket)(out) +} // RoutingTable defines the routing table. type RoutingTable struct { + // ID of the local peer + local ID + // kBuckets define all the fingers to other nodes. - Buckets []Bucket + Buckets []*Bucket + bucketsize int +} + +func convertPeerID(id peer.ID) ID { + hash := sha256.Sum256(id) + return hash[:] +} + +func convertKey(id u.Key) ID { + hash := sha256.Sum256([]byte(id)) + return hash[:] +} + +// Update adds or moves the given peer to the front of its respective bucket +// If a peer gets removed from a bucket, it is returned +func (rt *RoutingTable) Update(p *peer.Peer) *peer.Peer { + peer_id := convertPeerID(p.ID) + cpl := xor(peer_id, rt.local).commonPrefixLen() + + b_id := cpl + if b_id >= len(rt.Buckets) { + b_id = len(rt.Buckets) - 1 + } + + bucket := rt.Buckets[b_id] + e := bucket.Find(p.ID) + if e == nil { + // New peer, add to bucket + bucket.PushFront(p) + + // Are we past the max bucket size? + if bucket.Len() > rt.bucketsize { + if b_id == len(rt.Buckets) - 1 { + new_bucket := bucket.Split(b_id, rt.local) + rt.Buckets = append(rt.Buckets, new_bucket) + + // If all elements were on left side of split... + if bucket.Len() > rt.bucketsize { + return bucket.PopBack() + } + } else { + // If the bucket cant split kick out least active node + return bucket.PopBack() + } + } + return nil + } else { + // If the peer is already in the table, move it to the front. + // This signifies that it it "more active" and the less active nodes + // Will as a result tend towards the back of the list + bucket.MoveToFront(e) + return nil + } +} + +// A helper struct to sort peers by their distance to the local node +type peerDistance struct { + p *peer.Peer + distance ID +} +type peerSorterArr []*peerDistance +func (p peerSorterArr) Len() int {return len(p)} +func (p peerSorterArr) Swap(a, b int) {p[a],p[b] = p[b],p[a]} +func (p peerSorterArr) Less(a, b int) bool { + return p[a].distance.Less(p[b]) +} +// + +func (rt *RoutingTable) NearestPeer(id ID) *peer.Peer { + peers := rt.NearestPeers(id, 1) + return peers[0] } //TODO: make this accept an ID, requires method of converting keys to IDs -func (rt *RoutingTable) NearestNode(key u.Key) *peer.Peer { - panic("Function not implemented.") +func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { + cpl := xor(id, rt.local).commonPrefixLen() + + // Get bucket at cpl index or last bucket + var bucket *Bucket + if cpl >= len(rt.Buckets) { + bucket = rt.Buckets[len(rt.Buckets) - 1] + } else { + bucket = rt.Buckets[cpl] + } + + if bucket.Len() == 0 { + // This can happen, very rarely. + panic("Case not yet implemented.") + } + + var peerArr peerSorterArr + + plist := (*list.List)(bucket) + for e := plist.Front();e != nil; e = e.Next() { + p := e.Value.(*peer.Peer) + p_id := convertPeerID(p.ID) + pd := peerDistance{ + p: p, + distance: xor(rt.local, p_id), + } + peerArr = append(peerArr, &pd) + } + + sort.Sort(peerArr) + + var out []*peer.Peer + for i := 0; i < count && i < peerArr.Len(); i++ { + out = append(out, peerArr[i].p) + } + + return out } func (id ID) Equal(other ID) bool { From bade1aa277b8531ccead82384930adab8eb841f0 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Sun, 3 Aug 2014 15:04:24 -0700 Subject: [PATCH 06/30] tests for kbucket and some code cleanup --- routing/dht/bucket.go | 63 +++++++++++++++++ routing/dht/table.go | 143 +++++--------------------------------- routing/dht/table_test.go | 92 ++++++++++++++++++++++++ routing/dht/util.go | 78 +++++++++++++++++++++ 4 files changed, 249 insertions(+), 127 deletions(-) create mode 100644 routing/dht/bucket.go create mode 100644 routing/dht/table_test.go create mode 100644 routing/dht/util.go diff --git a/routing/dht/bucket.go b/routing/dht/bucket.go new file mode 100644 index 000000000..120ed29a4 --- /dev/null +++ b/routing/dht/bucket.go @@ -0,0 +1,63 @@ +package dht + +import ( + "container/list" + + peer "github.com/jbenet/go-ipfs/peer" +) +// Bucket holds a list of peers. +type Bucket list.List + +func (b *Bucket) Find(id peer.ID) *list.Element { + bucket_list := (*list.List)(b) + for e := bucket_list.Front(); e != nil; e = e.Next() { + if e.Value.(*peer.Peer).ID.Equal(id) { + return e + } + } + return nil +} + +func (b *Bucket) MoveToFront(e *list.Element) { + bucket_list := (*list.List)(b) + bucket_list.MoveToFront(e) +} + +func (b *Bucket) PushFront(p *peer.Peer) { + bucket_list := (*list.List)(b) + bucket_list.PushFront(p) +} + +func (b *Bucket) PopBack() *peer.Peer { + bucket_list := (*list.List)(b) + last := bucket_list.Back() + bucket_list.Remove(last) + return last.Value.(*peer.Peer) +} + +func (b *Bucket) Len() int { + bucket_list := (*list.List)(b) + return bucket_list.Len() +} + +// Splits a buckets peers into two buckets, the methods receiver will have +// peers with CPL equal to cpl, the returned bucket will have peers with CPL +// greater than cpl (returned bucket has closer peers) +func (b *Bucket) Split(cpl int, target ID) *Bucket { + bucket_list := (*list.List)(b) + out := list.New() + e := bucket_list.Front() + for e != nil { + peer_id := convertPeerID(e.Value.(*peer.Peer).ID) + peer_cpl := xor(peer_id, target).commonPrefixLen() + if peer_cpl > cpl { + cur := e + out.PushBack(e.Value) + e = e.Next() + bucket_list.Remove(cur) + continue + } + e = e.Next() + } + return (*Bucket)(out) +} diff --git a/routing/dht/table.go b/routing/dht/table.go index b8c20f8ab..c1eed534b 100644 --- a/routing/dht/table.go +++ b/routing/dht/table.go @@ -1,76 +1,12 @@ package dht import ( - "bytes" "container/list" "sort" - "crypto/sha256" - peer "github.com/jbenet/go-ipfs/peer" - u "github.com/jbenet/go-ipfs/util" ) -// ID for IpfsDHT should be a byte slice, to allow for simpler operations -// (xor). DHT ids are based on the peer.IDs. -// -// NOTE: peer.IDs are biased because they are multihashes (first bytes -// biased). Thus, may need to re-hash keys (uniform dist). TODO(jbenet) -type ID []byte - -// Bucket holds a list of peers. -type Bucket list.List - -func (b *Bucket) Find(id peer.ID) *list.Element { - bucket_list := (*list.List)(b) - for e := bucket_list.Front(); e != nil; e = e.Next() { - if e.Value.(*peer.Peer).ID.Equal(id) { - return e - } - } - return nil -} - -func (b *Bucket) MoveToFront(e *list.Element) { - bucket_list := (*list.List)(b) - bucket_list.MoveToFront(e) -} - -func (b *Bucket) PushFront(p *peer.Peer) { - bucket_list := (*list.List)(b) - bucket_list.PushFront(p) -} - -func (b *Bucket) PopBack() *peer.Peer { - bucket_list := (*list.List)(b) - last := bucket_list.Back() - bucket_list.Remove(last) - return last.Value.(*peer.Peer) -} - -func (b *Bucket) Len() int { - bucket_list := (*list.List)(b) - return bucket_list.Len() -} - -func (b *Bucket) Split(cpl int, target ID) *Bucket { - bucket_list := (*list.List)(b) - out := list.New() - e := bucket_list.Front() - for e != nil { - peer_id := convertPeerID(e.Value.(*peer.Peer).ID) - peer_cpl := xor(peer_id, target).commonPrefixLen() - if peer_cpl > cpl { - cur := e - out.PushBack(e.Value) - e = e.Next() - bucket_list.Remove(cur) - continue - } - } - return (*Bucket)(out) -} - // RoutingTable defines the routing table. type RoutingTable struct { @@ -82,14 +18,12 @@ type RoutingTable struct { bucketsize int } -func convertPeerID(id peer.ID) ID { - hash := sha256.Sum256(id) - return hash[:] -} - -func convertKey(id u.Key) ID { - hash := sha256.Sum256([]byte(id)) - return hash[:] +func NewRoutingTable(bucketsize int, local_id ID) *RoutingTable { + rt := new(RoutingTable) + rt.Buckets = []*Bucket{new(Bucket)} + rt.bucketsize = bucketsize + rt.local = local_id + return rt } // Update adds or moves the given peer to the front of its respective bucket @@ -114,6 +48,10 @@ func (rt *RoutingTable) Update(p *peer.Peer) *peer.Peer { if b_id == len(rt.Buckets) - 1 { new_bucket := bucket.Split(b_id, rt.local) rt.Buckets = append(rt.Buckets, new_bucket) + if new_bucket.Len() > rt.bucketsize { + // This is another very rare and annoying case + panic("Case not handled.") + } // If all elements were on left side of split... if bucket.Len() > rt.bucketsize { @@ -139,20 +77,23 @@ type peerDistance struct { p *peer.Peer distance ID } + +// peerSorterArr implements sort.Interface to sort peers by xor distance type peerSorterArr []*peerDistance func (p peerSorterArr) Len() int {return len(p)} func (p peerSorterArr) Swap(a, b int) {p[a],p[b] = p[b],p[a]} func (p peerSorterArr) Less(a, b int) bool { - return p[a].distance.Less(p[b]) + return p[a].distance.Less(p[b].distance) } // +// Returns a single peer that is nearest to the given ID func (rt *RoutingTable) NearestPeer(id ID) *peer.Peer { peers := rt.NearestPeers(id, 1) return peers[0] } -//TODO: make this accept an ID, requires method of converting keys to IDs +// Returns a list of the 'count' closest peers to the given ID func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { cpl := xor(id, rt.local).commonPrefixLen() @@ -170,7 +111,6 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { } var peerArr peerSorterArr - plist := (*list.List)(bucket) for e := plist.Front();e != nil; e = e.Next() { p := e.Value.(*peer.Peer) @@ -182,6 +122,7 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { peerArr = append(peerArr, &pd) } + // Sort by distance to local peer sort.Sort(peerArr) var out []*peer.Peer @@ -191,55 +132,3 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { return out } - -func (id ID) Equal(other ID) bool { - return bytes.Equal(id, other) -} - -func (id ID) Less(other interface{}) bool { - a, b := equalizeSizes(id, other.(ID)) - for i := 0; i < len(a); i++ { - if a[i] != b[i] { - return a[i] < b[i] - } - } - return len(a) < len(b) -} - -func (id ID) commonPrefixLen() int { - for i := 0; i < len(id); i++ { - for j := 0; j < 8; j++ { - if (id[i]>>uint8(7-j))&0x1 != 0 { - return i*8 + j - } - } - } - return len(id)*8 - 1 -} - -func xor(a, b ID) ID { - a, b = equalizeSizes(a, b) - - c := make(ID, len(a)) - for i := 0; i < len(a); i++ { - c[i] = a[i] ^ b[i] - } - return c -} - -func equalizeSizes(a, b ID) (ID, ID) { - la := len(a) - lb := len(b) - - if la < lb { - na := make([]byte, lb) - copy(na, a) - a = na - } else if lb < la { - nb := make([]byte, la) - copy(nb, b) - b = nb - } - - return a, b -} diff --git a/routing/dht/table_test.go b/routing/dht/table_test.go new file mode 100644 index 000000000..cb52bd1a0 --- /dev/null +++ b/routing/dht/table_test.go @@ -0,0 +1,92 @@ +package dht + +import ( + crand "crypto/rand" + "crypto/sha256" + "math/rand" + "container/list" + "testing" + + peer "github.com/jbenet/go-ipfs/peer" +) + +func _randPeer() *peer.Peer { + p := new(peer.Peer) + p.ID = make(peer.ID, 16) + crand.Read(p.ID) + return p +} + +func _randID() ID { + buf := make([]byte, 16) + crand.Read(buf) + + hash := sha256.Sum256(buf) + return ID(hash[:]) +} + +// Test basic features of the bucket struct +func TestBucket(t *testing.T) { + b := new(Bucket) + + peers := make([]*peer.Peer, 100) + for i := 0; i < 100; i++ { + peers[i] = _randPeer() + b.PushFront(peers[i]) + } + + local := _randPeer() + local_id := convertPeerID(local.ID) + + i := rand.Intn(len(peers)) + e := b.Find(peers[i].ID) + if e == nil { + t.Errorf("Failed to find peer: %v", peers[i]) + } + + spl := b.Split(0, convertPeerID(local.ID)) + llist := (*list.List)(b) + for e := llist.Front(); e != nil; e = e.Next() { + p := convertPeerID(e.Value.(*peer.Peer).ID) + cpl := xor(p, local_id).commonPrefixLen() + if cpl > 0 { + t.Fatalf("Split failed. found id with cpl > 0 in 0 bucket") + } + } + + rlist := (*list.List)(spl) + for e := rlist.Front(); e != nil; e = e.Next() { + p := convertPeerID(e.Value.(*peer.Peer).ID) + cpl := xor(p, local_id).commonPrefixLen() + if cpl == 0 { + t.Fatalf("Split failed. found id with cpl == 0 in non 0 bucket") + } + } +} + +// Right now, this just makes sure that it doesnt hang or crash +func TestTableUpdate(t *testing.T) { + local := _randPeer() + rt := NewRoutingTable(10, convertPeerID(local.ID)) + + peers := make([]*peer.Peer, 100) + for i := 0; i < 100; i++ { + peers[i] = _randPeer() + } + + // Testing Update + for i := 0; i < 10000; i++ { + p := rt.Update(peers[rand.Intn(len(peers))]) + if p != nil { + t.Log("evicted peer.") + } + } + + for i := 0; i < 100; i++ { + id := _randID() + ret := rt.NearestPeers(id, 5) + if len(ret) == 0 { + t.Fatal("Failed to find node near ID.") + } + } +} diff --git a/routing/dht/util.go b/routing/dht/util.go new file mode 100644 index 000000000..eed8d9301 --- /dev/null +++ b/routing/dht/util.go @@ -0,0 +1,78 @@ +package dht + +import ( + "bytes" + "crypto/sha256" + + peer "github.com/jbenet/go-ipfs/peer" + u "github.com/jbenet/go-ipfs/util" +) + +// ID for IpfsDHT should be a byte slice, to allow for simpler operations +// (xor). DHT ids are based on the peer.IDs. +// +// NOTE: peer.IDs are biased because they are multihashes (first bytes +// biased). Thus, may need to re-hash keys (uniform dist). TODO(jbenet) +type ID []byte + +func (id ID) Equal(other ID) bool { + return bytes.Equal(id, other) +} + +func (id ID) Less(other interface{}) bool { + a, b := equalizeSizes(id, other.(ID)) + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return a[i] < b[i] + } + } + return len(a) < len(b) +} + +func (id ID) commonPrefixLen() int { + for i := 0; i < len(id); i++ { + for j := 0; j < 8; j++ { + if (id[i]>>uint8(7-j))&0x1 != 0 { + return i*8 + j + } + } + } + return len(id)*8 - 1 +} + +func xor(a, b ID) ID { + a, b = equalizeSizes(a, b) + + c := make(ID, len(a)) + for i := 0; i < len(a); i++ { + c[i] = a[i] ^ b[i] + } + return c +} + +func equalizeSizes(a, b ID) (ID, ID) { + la := len(a) + lb := len(b) + + if la < lb { + na := make([]byte, lb) + copy(na, a) + a = na + } else if lb < la { + nb := make([]byte, la) + copy(nb, b) + b = nb + } + + return a, b +} + +func convertPeerID(id peer.ID) ID { + hash := sha256.Sum256(id) + return hash[:] +} + +func convertKey(id u.Key) ID { + hash := sha256.Sum256([]byte(id)) + return hash[:] +} From a85ce3fad31e66912a750e96f9d8bff87f6a1286 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Sun, 3 Aug 2014 17:35:12 -0700 Subject: [PATCH 07/30] finish implementation of Put and Get for DHT --- routing/dht/dht.go | 10 ++++--- routing/dht/dht_test.go | 60 ++++++++++++++++++++++++++++++++++++++++- routing/dht/routing.go | 58 ++++++++++++++++++++------------------- routing/dht/table.go | 53 +++++++++++++++++++++++------------- swarm/swarm.go | 7 ++--- 5 files changed, 136 insertions(+), 52 deletions(-) diff --git a/routing/dht/dht.go b/routing/dht/dht.go index ae320426c..02ef8c4f7 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -21,7 +21,7 @@ import ( // IpfsDHT is an implementation of Kademlia with Coral and S/Kademlia modifications. // It is used to implement the base IpfsRouting module. type IpfsDHT struct { - routes RoutingTable + routes *RoutingTable network *swarm.Swarm @@ -53,6 +53,7 @@ func NewDHT(p *peer.Peer) (*IpfsDHT, error) { dht.self = p dht.listeners = make(map[uint64]chan *swarm.Message) dht.shutdown = make(chan struct{}) + dht.routes = NewRoutingTable(20, convertPeerID(p.ID)) return dht, nil } @@ -78,14 +79,14 @@ func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) error { dht.network.StartConn(conn) - // TODO: Add this peer to our routing table + dht.routes.Update(peer) return nil } // Read in all messages from swarm and handle them appropriately // NOTE: this function is just a quick sketch func (dht *IpfsDHT) handleMessages() { - u.DOut("Being message handling routine") + u.DOut("Begin message handling routine") for { select { case mes := <-dht.network.Chan.Incoming: @@ -98,6 +99,9 @@ func (dht *IpfsDHT) handleMessages() { continue } + // Update peers latest visit in routing table + dht.routes.Update(mes.Peer) + // Note: not sure if this is the correct place for this if pmes.GetResponse() { dht.listenLock.RLock() diff --git a/routing/dht/dht_test.go b/routing/dht/dht_test.go index 8485a1d83..0c4b7ee09 100644 --- a/routing/dht/dht_test.go +++ b/routing/dht/dht_test.go @@ -6,9 +6,13 @@ import ( ma "github.com/jbenet/go-multiaddr" u "github.com/jbenet/go-ipfs/util" + "fmt" + "time" ) +var _ = fmt.Println + func TestPing(t *testing.T) { u.Debug = false addr_a,err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/1234") @@ -38,7 +42,6 @@ func TestPing(t *testing.T) { t.Fatal(err) } - dht_a.Start() dht_b.Start() @@ -52,4 +55,59 @@ func TestPing(t *testing.T) { if err != nil { t.Fatal(err) } + + dht_a.Halt() + dht_b.Halt() +} + +func TestValueGetSet(t *testing.T) { + u.Debug = false + addr_a,err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/1235") + if err != nil { + t.Fatal(err) + } + addr_b,err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5679") + if err != nil { + t.Fatal(err) + } + + peer_a := new(peer.Peer) + peer_a.AddAddress(addr_a) + peer_a.ID = peer.ID([]byte("peer_a")) + + peer_b := new(peer.Peer) + peer_b.AddAddress(addr_b) + peer_b.ID = peer.ID([]byte("peer_b")) + + dht_a,err := NewDHT(peer_a) + if err != nil { + t.Fatal(err) + } + + dht_b,err := NewDHT(peer_b) + if err != nil { + t.Fatal(err) + } + + dht_a.Start() + dht_b.Start() + + err = dht_a.Connect(addr_b) + if err != nil { + t.Fatal(err) + } + + err = dht_a.PutValue("hello", []byte("world")) + if err != nil { + t.Fatal(err) + } + + val, err := dht_a.GetValue("hello", time.Second * 2) + if err != nil { + t.Fatal(err) + } + + if string(val) != "world" { + t.Fatalf("Expected 'world' got %s", string(val)) + } } diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 66e27e369..2b37192cd 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -4,6 +4,8 @@ import ( "math/rand" "time" + proto "code.google.com/p/goprotobuf/proto" + peer "github.com/jbenet/go-ipfs/peer" swarm "github.com/jbenet/go-ipfs/swarm" u "github.com/jbenet/go-ipfs/util" @@ -22,21 +24,20 @@ func GenerateMessageID() uint64 { func (s *IpfsDHT) PutValue(key u.Key, value []byte) error { var p *peer.Peer p = s.routes.NearestPeer(convertKey(key)) + if p == nil { + u.POut("nbuckets: %d", len(s.routes.Buckets)) + u.POut("%d", s.routes.Buckets[0].Len()) + panic("Table returned nil peer!") + } - pmes_type := DHTMessage_PUT_VALUE - str_key := string(key) - mes_id := GenerateMessageID() - - pmes := new(DHTMessage) - pmes.Type = &pmes_type - pmes.Key = &str_key - pmes.Value = value - pmes.Id = &mes_id - - mes := new(swarm.Message) - mes.Data = []byte(pmes.String()) - mes.Peer = p + pmes := pDHTMessage{ + Type: DHTMessage_PUT_VALUE, + Key: string(key), + Value: value, + Id: GenerateMessageID(), + } + mes := swarm.NewMessage(p, pmes.ToProtobuf()) s.network.Chan.Outgoing <- mes return nil } @@ -45,21 +46,19 @@ func (s *IpfsDHT) PutValue(key u.Key, value []byte) error { func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { var p *peer.Peer p = s.routes.NearestPeer(convertKey(key)) + if p == nil { + panic("Table returned nil peer!") + } - str_key := string(key) - mes_type := DHTMessage_GET_VALUE - mes_id := GenerateMessageID() - // protobuf structure - pmes := new(DHTMessage) - pmes.Type = &mes_type - pmes.Key = &str_key - pmes.Id = &mes_id + pmes := pDHTMessage{ + Type: DHTMessage_GET_VALUE, + Key: string(key), + Id: GenerateMessageID(), + } + response_chan := s.ListenFor(pmes.Id) - mes := new(swarm.Message) - mes.Data = []byte(pmes.String()) - mes.Peer = p - - response_chan := s.ListenFor(*pmes.Id) + mes := swarm.NewMessage(p, pmes.ToProtobuf()) + s.network.Chan.Outgoing <- mes // Wait for either the response or a timeout timeup := time.After(timeout) @@ -68,7 +67,12 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { // TODO: unregister listener return nil, u.ErrTimeout case resp := <-response_chan: - return resp.Data, nil + pmes_out := new(DHTMessage) + err := proto.Unmarshal(resp.Data, pmes_out) + if err != nil { + return nil,err + } + return pmes_out.GetValue(), nil } } diff --git a/routing/dht/table.go b/routing/dht/table.go index c1eed534b..17af95756 100644 --- a/routing/dht/table.go +++ b/routing/dht/table.go @@ -49,7 +49,7 @@ func (rt *RoutingTable) Update(p *peer.Peer) *peer.Peer { new_bucket := bucket.Split(b_id, rt.local) rt.Buckets = append(rt.Buckets, new_bucket) if new_bucket.Len() > rt.bucketsize { - // This is another very rare and annoying case + // TODO: This is a very rare and annoying case panic("Case not handled.") } @@ -87,10 +87,27 @@ func (p peerSorterArr) Less(a, b int) bool { } // +func (rt *RoutingTable) copyPeersFromList(peerArr peerSorterArr, peerList *list.List) peerSorterArr { + for e := peerList.Front(); e != nil; e = e.Next() { + p := e.Value.(*peer.Peer) + p_id := convertPeerID(p.ID) + pd := peerDistance{ + p: p, + distance: xor(rt.local, p_id), + } + peerArr = append(peerArr, &pd) + } + return peerArr +} + // Returns a single peer that is nearest to the given ID func (rt *RoutingTable) NearestPeer(id ID) *peer.Peer { peers := rt.NearestPeers(id, 1) - return peers[0] + if len(peers) > 0 { + return peers[0] + } else { + return nil + } } // Returns a list of the 'count' closest peers to the given ID @@ -100,26 +117,26 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { // Get bucket at cpl index or last bucket var bucket *Bucket if cpl >= len(rt.Buckets) { - bucket = rt.Buckets[len(rt.Buckets) - 1] - } else { - bucket = rt.Buckets[cpl] - } - - if bucket.Len() == 0 { - // This can happen, very rarely. - panic("Case not yet implemented.") + cpl = len(rt.Buckets) - 1 } + bucket = rt.Buckets[cpl] var peerArr peerSorterArr - plist := (*list.List)(bucket) - for e := plist.Front();e != nil; e = e.Next() { - p := e.Value.(*peer.Peer) - p_id := convertPeerID(p.ID) - pd := peerDistance{ - p: p, - distance: xor(rt.local, p_id), + if bucket.Len() == 0 { + // In the case of an unusual split, one bucket may be empty. + // if this happens, search both surrounding buckets for nearest peer + if cpl > 0 { + plist := (*list.List)(rt.Buckets[cpl - 1]) + peerArr = rt.copyPeersFromList(peerArr, plist) } - peerArr = append(peerArr, &pd) + + if cpl < len(rt.Buckets) - 1 { + plist := (*list.List)(rt.Buckets[cpl + 1]) + peerArr = rt.copyPeersFromList(peerArr, plist) + } + } else { + plist := (*list.List)(bucket) + peerArr = rt.copyPeersFromList(peerArr, plist) } // Sort by distance to local peer diff --git a/swarm/swarm.go b/swarm/swarm.go index d6cbcc721..7b39570ab 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -101,7 +101,7 @@ func (s *Swarm) Listen() error { for i, addr := range s.local.Addresses { err := s.connListen(addr) if err != nil { - if ret_err != nil { + if ret_err == nil { ret_err = new(SwarmListenErr) ret_err.Errors = make([]error, len(s.local.Addresses)) } @@ -135,8 +135,9 @@ func (s *Swarm) connListen(maddr *ma.Multiaddr) error { for { nconn, err := list.Accept() if err != nil { - u.PErr("Failed to accept connection: %s - %s [%s]", netstr, - addr, err) + e := fmt.Errorf("Failed to accept connection: %s - %s [%s]", + netstr, addr, err) + s.Chan.Errors <- e return } go s.handleNewConn(nconn) From 248e06f759288d07100412c5b1838ce6600cb3ac Mon Sep 17 00:00:00 2001 From: Jeromy Date: Sun, 3 Aug 2014 21:46:01 -0700 Subject: [PATCH 08/30] working towards Providers implementation --- routing/dht/dht.go | 42 +++++++++++++++++++++-- routing/dht/routing.go | 78 ++++++++++++++++++++++++++++++++++++++---- routing/routing.go | 2 +- 3 files changed, 112 insertions(+), 10 deletions(-) diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 02ef8c4f7..e0c665051 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -3,6 +3,7 @@ package dht import ( "sync" "time" + "encoding/json" peer "github.com/jbenet/go-ipfs/peer" swarm "github.com/jbenet/go-ipfs/swarm" @@ -31,6 +32,10 @@ type IpfsDHT struct { // Local data datastore ds.Datastore + // Map keys to peers that can provide their value + // TODO: implement a TTL on each of these keys + providers map[u.Key][]*peer.Peer + // map of channels waiting for reply messages listeners map[uint64]chan *swarm.Message listenLock sync.RWMutex @@ -185,11 +190,44 @@ func (dht *IpfsDHT) handleFindNode(p *peer.Peer, pmes *DHTMessage) { } func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *DHTMessage) { - panic("Not implemented.") + providers := dht.providers[u.Key(pmes.GetKey())] + if providers == nil || len(providers) == 0 { + // ????? + } + + var addrs []string + for _,prov := range providers { + ma := prov.NetAddress("tcp") + str,err := ma.String() + if err != nil { + u.PErr("Error: %s", err) + continue + } + + addrs = append(addrs, str) + } + + data,err := json.Marshal(addrs) + if err != nil { + panic(err) + } + + resp := pDHTMessage{ + Type: DHTMessage_GET_PROVIDERS, + Key: pmes.GetKey(), + Value: data, + Id: pmes.GetId(), + } + + mes := swarm.NewMessage(p, resp.ToProtobuf()) + dht.network.Chan.Outgoing <-mes } func (dht *IpfsDHT) handleAddProvider(p *peer.Peer, pmes *DHTMessage) { - panic("Not implemented.") + //TODO: need to implement TTLs on providers + key := u.Key(pmes.GetKey()) + parr := dht.providers[key] + dht.providers[key] = append(parr, p) } diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 2b37192cd..699242f56 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -11,6 +11,9 @@ import ( u "github.com/jbenet/go-ipfs/util" ) +// Pool size is the number of nodes used for group find/set RPC calls +var PoolSize = 6 + // TODO: determine a way of creating and managing message IDs func GenerateMessageID() uint64 { return uint64(rand.Uint32()) << 32 & uint64(rand.Uint32()) @@ -25,8 +28,6 @@ func (s *IpfsDHT) PutValue(key u.Key, value []byte) error { var p *peer.Peer p = s.routes.NearestPeer(convertKey(key)) if p == nil { - u.POut("nbuckets: %d", len(s.routes.Buckets)) - u.POut("%d", s.routes.Buckets[0].Len()) panic("Table returned nil peer!") } @@ -64,7 +65,7 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { timeup := time.After(timeout) select { case <-timeup: - // TODO: unregister listener + s.Unlisten(pmes.Id) return nil, u.ErrTimeout case resp := <-response_chan: pmes_out := new(DHTMessage) @@ -81,17 +82,80 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { // Announce that this node can provide value for given key func (s *IpfsDHT) Provide(key u.Key) error { - return u.ErrNotImplemented + peers := s.routes.NearestPeers(convertKey(key), PoolSize) + if len(peers) == 0 { + //return an error + } + + pmes := pDHTMessage{ + Type: DHTMessage_ADD_PROVIDER, + Key: string(key), + } + pbmes := pmes.ToProtobuf() + + for _,p := range peers { + mes := swarm.NewMessage(p, pbmes) + s.network.Chan.Outgoing <-mes + } + return nil } // FindProviders searches for peers who can provide the value for given key. -func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) (*peer.Peer, error) { - return nil, u.ErrNotImplemented +func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, error) { + p := s.routes.NearestPeer(convertKey(key)) + + pmes := pDHTMessage{ + Type: DHTMessage_GET_PROVIDERS, + Key: string(key), + Id: GenerateMessageID(), + } + + mes := swarm.NewMessage(p, pmes.ToProtobuf()) + + listen_chan := s.ListenFor(pmes.Id) + s.network.Chan.Outgoing <-mes + after := time.After(timeout) + select { + case <-after: + s.Unlisten(pmes.Id) + return nil, u.ErrTimeout + case resp := <-listen_chan: + pmes_out := new(DHTMessage) + err := proto.Unmarshal(resp.Data, pmes_out) + if err != nil { + return nil, err + } + panic("Not yet implemented.") + } } // Find specific Peer // FindPeer searches for a peer with given ID. func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error) { - return nil, u.ErrNotImplemented + p := s.routes.NearestPeer(convertPeerID(id)) + + pmes := pDHTMessage{ + Type: DHTMessage_FIND_NODE, + Key: string(id), + Id: GenerateMessageID(), + } + + mes := swarm.NewMessage(p, pmes.ToProtobuf()) + + listen_chan := s.ListenFor(pmes.Id) + s.network.Chan.Outgoing <-mes + after := time.After(timeout) + select { + case <-after: + s.Unlisten(pmes.Id) + return nil, u.ErrTimeout + case resp := <-listen_chan: + pmes_out := new(DHTMessage) + err := proto.Unmarshal(resp.Data, pmes_out) + if err != nil { + return nil, err + } + panic("Not yet implemented.") + } } diff --git a/routing/routing.go b/routing/routing.go index 933032f46..3826f13cb 100644 --- a/routing/routing.go +++ b/routing/routing.go @@ -25,7 +25,7 @@ type IpfsRouting interface { Provide(key u.Key) error // FindProviders searches for peers who can provide the value for given key. - FindProviders(key u.Key, timeout time.Duration) (*peer.Peer, error) + FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, error) // Find specific Peer From 3a76ef047836b4585e90a0d7893174751fa51aaa Mon Sep 17 00:00:00 2001 From: Jeromy Date: Tue, 5 Aug 2014 09:38:26 -0700 Subject: [PATCH 09/30] a little error handling and some work on providers --- routing/dht/dht.go | 16 ++++++++++++---- routing/dht/routing.go | 30 +++++++++++++++++++++++++++++- swarm/swarm.go | 3 +++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/routing/dht/dht.go b/routing/dht/dht.go index e0c665051..0341218ae 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -35,6 +35,7 @@ type IpfsDHT struct { // Map keys to peers that can provide their value // TODO: implement a TTL on each of these keys providers map[u.Key][]*peer.Peer + providerLock sync.RWMutex // map of channels waiting for reply messages listeners map[uint64]chan *swarm.Message @@ -46,6 +47,9 @@ type IpfsDHT struct { // Create a new DHT object with the given peer as the 'local' host func NewDHT(p *peer.Peer) (*IpfsDHT, error) { + if p == nil { + panic("Tried to create new dht with nil peer") + } network := swarm.NewSwarm(p) err := network.Listen() if err != nil { @@ -68,24 +72,27 @@ func (dht *IpfsDHT) Start() { } // Connect to a new peer at the given address -func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) error { +func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { + if addr == nil { + panic("addr was nil!") + } peer := new(peer.Peer) peer.AddAddress(addr) conn,err := swarm.Dial("tcp", peer) if err != nil { - return err + return nil, err } err = identify.Handshake(dht.self, peer, conn.Incoming.MsgChan, conn.Outgoing.MsgChan) if err != nil { - return err + return nil, err } dht.network.StartConn(conn) dht.routes.Update(peer) - return nil + return peer, nil } // Read in all messages from swarm and handle them appropriately @@ -195,6 +202,7 @@ func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *DHTMessage) { // ????? } + // This is just a quick hack, formalize method of sending addrs later var addrs []string for _,prov := range providers { ma := prov.NetAddress("tcp") diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 699242f56..0180998d9 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -3,9 +3,12 @@ package dht import ( "math/rand" "time" + "encoding/json" proto "code.google.com/p/goprotobuf/proto" + ma "github.com/jbenet/go-multiaddr" + peer "github.com/jbenet/go-ipfs/peer" swarm "github.com/jbenet/go-ipfs/swarm" u "github.com/jbenet/go-ipfs/util" @@ -125,7 +128,32 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, if err != nil { return nil, err } - panic("Not yet implemented.") + var addrs map[string]string + err := json.Unmarshal(pmes_out.GetValue(), &addrs) + if err != nil { + return nil, err + } + + for key,addr := range addrs { + p := s.network.Find(u.Key(key)) + if p == nil { + maddr,err := ma.NewMultiaddr(addr) + if err != nil { + u.PErr("error connecting to new peer: %s", err) + continue + } + p, err := s.Connect(maddr) + if err != nil { + u.PErr("error connecting to new peer: %s", err) + continue + } + } + s.providerLock.Lock() + prov_arr := s.providers[key] + s.providers[key] = append(prov_arr, p) + s.providerLock.Unlock() + } + } } diff --git a/swarm/swarm.go b/swarm/swarm.go index 7b39570ab..676e26fbf 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -290,3 +290,6 @@ Loop: delete(s.conns, conn.Peer.Key()) s.connsLock.Unlock() } + +func (s *Swarm) Find(addr *ma.Multiaddr) { +} From 71c7c5844a5274b55243277690241d255b93f1af Mon Sep 17 00:00:00 2001 From: Jeromy Date: Tue, 5 Aug 2014 18:32:22 -0700 Subject: [PATCH 10/30] providers interface is coming along nicely --- identify/identify.go | 2 +- peer/peer.go | 6 ++++++ routing/dht/dht.go | 39 +++++++++++++++++++++++++++----------- routing/dht/pDHTMessage.go | 11 +++++++++++ routing/dht/routing.go | 24 +++++++++++++---------- swarm/swarm.go | 28 ++++++++++++++------------- util/util.go | 5 +++++ 7 files changed, 80 insertions(+), 35 deletions(-) diff --git a/identify/identify.go b/identify/identify.go index 5d12b3cab..2a0ddcd37 100644 --- a/identify/identify.go +++ b/identify/identify.go @@ -20,7 +20,7 @@ func Handshake(self, remote *peer.Peer, in, out chan []byte) error { out <- self.ID resp := <-in remote.ID = peer.ID(resp) - u.DOut("Got node id: %s", string(remote.ID)) + u.DOut("identify: Got node id: %s", remote.ID.Pretty()) return nil } diff --git a/peer/peer.go b/peer/peer.go index 3af7e27e6..0357ab552 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -1,6 +1,8 @@ package peer import ( + "encoding/hex" + u "github.com/jbenet/go-ipfs/util" ma "github.com/jbenet/go-multiaddr" mh "github.com/jbenet/go-multihash" @@ -16,6 +18,10 @@ func (id ID) Equal(other ID) bool { return bytes.Equal(id, other) } +func (id ID) Pretty() string { + return hex.EncodeToString(id) +} + // Map maps Key (string) : *Peer (slices are not comparable). type Map map[u.Key]*Peer diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 0341218ae..c12e35190 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -61,6 +61,7 @@ func NewDHT(p *peer.Peer) (*IpfsDHT, error) { dht.datastore = ds.NewMapDatastore() dht.self = p dht.listeners = make(map[uint64]chan *swarm.Message) + dht.providers = make(map[u.Key][]*peer.Peer) dht.shutdown = make(chan struct{}) dht.routes = NewRoutingTable(20, convertPeerID(p.ID)) return dht, nil @@ -101,9 +102,11 @@ func (dht *IpfsDHT) handleMessages() { u.DOut("Begin message handling routine") for { select { - case mes := <-dht.network.Chan.Incoming: - u.DOut("recieved message from swarm.") - + case mes,ok := <-dht.network.Chan.Incoming: + if !ok { + u.DOut("handleMessages closing, bad recv on incoming") + return + } pmes := new(DHTMessage) err := proto.Unmarshal(mes.Data, pmes) if err != nil { @@ -121,15 +124,16 @@ func (dht *IpfsDHT) handleMessages() { dht.listenLock.RUnlock() if ok { ch <- mes + } else { + // this is expected behaviour during a timeout + u.DOut("Received response with nobody listening...") } - // this is expected behaviour during a timeout - u.DOut("Received response with nobody listening...") continue } // - u.DOut("Got message type: %d", pmes.GetType()) + u.DOut("Got message type: '%s' [id = %x]", mesNames[pmes.GetType()], pmes.GetId()) switch pmes.GetType() { case DHTMessage_GET_VALUE: dht.handleGetValue(mes.Peer, pmes) @@ -138,13 +142,15 @@ func (dht *IpfsDHT) handleMessages() { case DHTMessage_FIND_NODE: dht.handleFindNode(mes.Peer, pmes) case DHTMessage_ADD_PROVIDER: + dht.handleAddProvider(mes.Peer, pmes) case DHTMessage_GET_PROVIDERS: + dht.handleGetProviders(mes.Peer, pmes) case DHTMessage_PING: dht.handlePing(mes.Peer, pmes) } case err := <-dht.network.Chan.Errors: - panic(err) + u.DErr("dht err: %s", err) case <-dht.shutdown: return } @@ -197,13 +203,16 @@ func (dht *IpfsDHT) handleFindNode(p *peer.Peer, pmes *DHTMessage) { } func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *DHTMessage) { + dht.providerLock.RLock() providers := dht.providers[u.Key(pmes.GetKey())] + dht.providerLock.RUnlock() if providers == nil || len(providers) == 0 { // ????? + u.DOut("No known providers for requested key.") } // This is just a quick hack, formalize method of sending addrs later - var addrs []string + addrs := make(map[u.Key]string) for _,prov := range providers { ma := prov.NetAddress("tcp") str,err := ma.String() @@ -212,7 +221,7 @@ func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *DHTMessage) { continue } - addrs = append(addrs, str) + addrs[prov.Key()] = str } data,err := json.Marshal(addrs) @@ -225,6 +234,7 @@ func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *DHTMessage) { Key: pmes.GetKey(), Value: data, Id: pmes.GetId(), + Response: true, } mes := swarm.NewMessage(p, resp.ToProtobuf()) @@ -234,8 +244,7 @@ func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *DHTMessage) { func (dht *IpfsDHT) handleAddProvider(p *peer.Peer, pmes *DHTMessage) { //TODO: need to implement TTLs on providers key := u.Key(pmes.GetKey()) - parr := dht.providers[key] - dht.providers[key] = append(parr, p) + dht.addProviderEntry(key, p) } @@ -290,3 +299,11 @@ func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { return u.ErrTimeout } } + +func (dht *IpfsDHT) addProviderEntry(key u.Key, p *peer.Peer) { + u.DOut("Adding %s as provider for '%s'", p.Key().Pretty(), key) + dht.providerLock.Lock() + provs := dht.providers[key] + dht.providers[key] = append(provs, p) + dht.providerLock.Unlock() +} diff --git a/routing/dht/pDHTMessage.go b/routing/dht/pDHTMessage.go index 65c03b1f8..8b862dbc9 100644 --- a/routing/dht/pDHTMessage.go +++ b/routing/dht/pDHTMessage.go @@ -9,6 +9,17 @@ type pDHTMessage struct { Id uint64 } +var mesNames [10]string + +func init() { + mesNames[DHTMessage_ADD_PROVIDER] = "add provider" + mesNames[DHTMessage_FIND_NODE] = "find node" + mesNames[DHTMessage_GET_PROVIDERS] = "get providers" + mesNames[DHTMessage_GET_VALUE] = "get value" + mesNames[DHTMessage_PUT_VALUE] = "put value" + mesNames[DHTMessage_PING] = "ping" +} + func (m *pDHTMessage) ToProtobuf() *DHTMessage { pmes := new(DHTMessage) if m.Value != nil { diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 0180998d9..cbe0e9246 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -19,7 +19,8 @@ var PoolSize = 6 // TODO: determine a way of creating and managing message IDs func GenerateMessageID() uint64 { - return uint64(rand.Uint32()) << 32 & uint64(rand.Uint32()) + //return (uint64(rand.Uint32()) << 32) & uint64(rand.Uint32()) + return uint64(rand.Uint32()) } // This file implements the Routing interface for the IpfsDHT struct. @@ -116,6 +117,7 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, mes := swarm.NewMessage(p, pmes.ToProtobuf()) listen_chan := s.ListenFor(pmes.Id) + u.DOut("Find providers for: '%s'", key) s.network.Chan.Outgoing <-mes after := time.After(timeout) select { @@ -123,37 +125,39 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, s.Unlisten(pmes.Id) return nil, u.ErrTimeout case resp := <-listen_chan: + u.DOut("FindProviders: got response.") pmes_out := new(DHTMessage) err := proto.Unmarshal(resp.Data, pmes_out) if err != nil { return nil, err } - var addrs map[string]string - err := json.Unmarshal(pmes_out.GetValue(), &addrs) + var addrs map[u.Key]string + err = json.Unmarshal(pmes_out.GetValue(), &addrs) if err != nil { return nil, err } - for key,addr := range addrs { - p := s.network.Find(u.Key(key)) + var prov_arr []*peer.Peer + for pid,addr := range addrs { + p := s.network.Find(pid) if p == nil { maddr,err := ma.NewMultiaddr(addr) if err != nil { u.PErr("error connecting to new peer: %s", err) continue } - p, err := s.Connect(maddr) + p, err = s.Connect(maddr) if err != nil { u.PErr("error connecting to new peer: %s", err) continue } } - s.providerLock.Lock() - prov_arr := s.providers[key] - s.providers[key] = append(prov_arr, p) - s.providerLock.Unlock() + s.addProviderEntry(key, p) + prov_arr = append(prov_arr, p) } + return prov_arr, nil + } } diff --git a/swarm/swarm.go b/swarm/swarm.go index 676e26fbf..451de76ed 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -47,7 +47,7 @@ func NewChan(bufsize int) *Chan { return &Chan{ Outgoing: make(chan *Message, bufsize), Incoming: make(chan *Message, bufsize), - Errors: make(chan error), + Errors: make(chan error, bufsize), Close: make(chan bool, bufsize), } } @@ -137,7 +137,7 @@ func (s *Swarm) connListen(maddr *ma.Multiaddr) error { if err != nil { e := fmt.Errorf("Failed to accept connection: %s - %s [%s]", netstr, addr, err) - s.Chan.Errors <- e + go func() {s.Chan.Errors <- e}() return } go s.handleNewConn(nconn) @@ -217,7 +217,7 @@ func (s *Swarm) StartConn(conn *Conn) { panic("tried to start nil Conn!") } - u.DOut("Starting connection: %s", string(conn.Peer.ID)) + u.DOut("Starting connection: %s", conn.Peer.Key().Pretty()) // add to conns s.connsLock.Lock() s.conns[conn.Peer.Key()] = conn @@ -234,10 +234,10 @@ func (s *Swarm) fanOut() { case <-s.Chan.Close: return // told to close. case msg, ok := <-s.Chan.Outgoing: - u.DOut("fanOut: outgoing message for: '%s'", msg.Peer.Key()) if !ok { return } + u.DOut("fanOut: outgoing message for: '%s'", msg.Peer.Key().Pretty()) s.connsLock.RLock() conn, found := s.conns[msg.Peer.Key()] @@ -252,7 +252,6 @@ func (s *Swarm) fanOut() { // queue it in the connection's buffer conn.Outgoing.MsgChan <- msg.Data - u.DOut("fanOut: message off.") } } } @@ -260,36 +259,39 @@ func (s *Swarm) fanOut() { // Handles the receiving + wrapping of messages, per conn. // Consider using reflect.Select with one goroutine instead of n. func (s *Swarm) fanIn(conn *Conn) { -Loop: for { select { case <-s.Chan.Close: // close Conn. conn.Close() - break Loop + goto out case <-conn.Closed: - break Loop + goto out case data, ok := <-conn.Incoming.MsgChan: - u.DOut("fanIn: got message from incoming channel.") if !ok { - e := fmt.Errorf("Error retrieving from conn: %v", conn) + e := fmt.Errorf("Error retrieving from conn: %v", conn.Peer.Key().Pretty()) s.Chan.Errors <- e - break Loop + goto out } // wrap it for consumers. msg := &Message{Peer: conn.Peer, Data: data} s.Chan.Incoming <- msg - u.DOut("fanIn: message off.") } } +out: s.connsLock.Lock() delete(s.conns, conn.Peer.Key()) s.connsLock.Unlock() } -func (s *Swarm) Find(addr *ma.Multiaddr) { +func (s *Swarm) Find(key u.Key) *peer.Peer { + conn, found := s.conns[key] + if !found { + return nil + } + return conn.Peer } diff --git a/util/util.go b/util/util.go index d54be111e..f5eec56bb 100644 --- a/util/util.go +++ b/util/util.go @@ -6,6 +6,7 @@ import ( "os" "os/user" "strings" + "encoding/hex" ) // Debug is a global flag for debugging. @@ -20,6 +21,10 @@ var ErrTimeout = fmt.Errorf("Error: Call timed out.") // Key is a string representation of multihash for use with maps. type Key string +func (k Key) Pretty() string { + return hex.EncodeToString([]byte(k)) +} + // Hash is the global IPFS hash function. uses multihash SHA2_256, 256 bits func Hash(data []byte) (mh.Multihash, error) { return mh.Sum(data, mh.SHA2_256, -1) From dc451fba2d325d2e157e122f5d59e480f89c9131 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Tue, 5 Aug 2014 20:31:48 -0700 Subject: [PATCH 11/30] implement find peer rpc --- identify/identify.go | 8 +----- routing/dht/dht.go | 65 +++++++++++++++++++++++++++++++++--------- routing/dht/routing.go | 8 +++++- swarm/swarm.go | 8 ++++++ 4 files changed, 67 insertions(+), 22 deletions(-) diff --git a/identify/identify.go b/identify/identify.go index 2a0ddcd37..b6c67f2c5 100644 --- a/identify/identify.go +++ b/identify/identify.go @@ -10,13 +10,7 @@ import ( // Perform initial communication with this peer to share node ID's and // initiate communication func Handshake(self, remote *peer.Peer, in, out chan []byte) error { - - // temporary: - // put your own id in a 16byte buffer and send that over to - // the peer as your ID, then wait for them to send their ID. - // Once that trade is finished, the handshake is complete and - // both sides should 'trust' each other - + // TODO: make this more... secure. out <- self.ID resp := <-in remote.ID = peer.ID(resp) diff --git a/routing/dht/dht.go b/routing/dht/dht.go index c12e35190..c2c1b63be 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -73,6 +73,7 @@ func (dht *IpfsDHT) Start() { } // Connect to a new peer at the given address +// TODO: move this into swarm func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { if addr == nil { panic("addr was nil!") @@ -90,9 +91,21 @@ func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { return nil, err } + // Send node an address that you can be reached on + myaddr := dht.self.NetAddress("tcp") + mastr,err := myaddr.String() + if err != nil { + panic("No local address to send") + } + + conn.Outgoing.MsgChan <- []byte(mastr) + dht.network.StartConn(conn) - dht.routes.Update(peer) + removed := dht.routes.Update(peer) + if removed != nil { + panic("need to remove this peer.") + } return peer, nil } @@ -115,7 +128,10 @@ func (dht *IpfsDHT) handleMessages() { } // Update peers latest visit in routing table - dht.routes.Update(mes.Peer) + removed := dht.routes.Update(mes.Peer) + if removed != nil { + panic("Need to handle removed peer.") + } // Note: not sure if this is the correct place for this if pmes.GetResponse() { @@ -140,7 +156,7 @@ func (dht *IpfsDHT) handleMessages() { case DHTMessage_PUT_VALUE: dht.handlePutValue(mes.Peer, pmes) case DHTMessage_FIND_NODE: - dht.handleFindNode(mes.Peer, pmes) + dht.handleFindPeer(mes.Peer, pmes) case DHTMessage_ADD_PROVIDER: dht.handleAddProvider(mes.Peer, pmes) case DHTMessage_GET_PROVIDERS: @@ -171,14 +187,14 @@ func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *DHTMessage) { mes := swarm.NewMessage(p, resp.ToProtobuf()) dht.network.Chan.Outgoing <- mes } else if err == ds.ErrNotFound { - // Find closest node(s) to desired key and reply with that info + // Find closest peer(s) to desired key and reply with that info // TODO: this will need some other metadata in the protobuf message - // to signal to the querying node that the data its receiving - // is actually a list of other nodes + // to signal to the querying peer that the data its receiving + // is actually a list of other peer } } -// Store a value in this nodes local storage +// Store a value in this peer local storage func (dht *IpfsDHT) handlePutValue(p *peer.Peer, pmes *DHTMessage) { dskey := ds.NewKey(pmes.GetKey()) err := dht.datastore.Put(dskey, pmes.GetValue()) @@ -189,7 +205,7 @@ func (dht *IpfsDHT) handlePutValue(p *peer.Peer, pmes *DHTMessage) { } func (dht *IpfsDHT) handlePing(p *peer.Peer, pmes *DHTMessage) { - resp := &pDHTMessage{ + resp := pDHTMessage{ Type: pmes.GetType(), Response: true, Id: pmes.GetId(), @@ -198,8 +214,29 @@ func (dht *IpfsDHT) handlePing(p *peer.Peer, pmes *DHTMessage) { dht.network.Chan.Outgoing <-swarm.NewMessage(p, resp.ToProtobuf()) } -func (dht *IpfsDHT) handleFindNode(p *peer.Peer, pmes *DHTMessage) { - panic("Not implemented.") +func (dht *IpfsDHT) handleFindPeer(p *peer.Peer, pmes *DHTMessage) { + closest := dht.routes.NearestPeer(convertKey(u.Key(pmes.GetKey()))) + if closest == nil { + } + + if len(closest.Addresses) == 0 { + panic("no addresses for connected peer...") + } + + addr,err := closest.Addresses[0].String() + if err != nil { + panic(err) + } + + resp := pDHTMessage{ + Type: pmes.GetType(), + Response: true, + Id: pmes.GetId(), + Value: []byte(addr), + } + + mes := swarm.NewMessage(p, resp.ToProtobuf()) + dht.network.Chan.Outgoing <-mes } func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *DHTMessage) { @@ -269,13 +306,13 @@ func (dht *IpfsDHT) Unlisten(mesid uint64) { close(ch) } -// Stop all communications from this node and shut down +// Stop all communications from this peer and shut down func (dht *IpfsDHT) Halt() { dht.shutdown <- struct{}{} dht.network.Close() } -// Ping a node, log the time it took +// Ping a peer, log the time it took func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { // Thoughts: maybe this should accept an ID and do a peer lookup? u.DOut("Enter Ping.") @@ -294,8 +331,8 @@ func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { u.POut("Ping took %s.", roundtrip.String()) return nil case <-tout: - // Timed out, think about removing node from network - u.DOut("Ping node timed out.") + // Timed out, think about removing peer from network + u.DOut("Ping peer timed out.") return u.ErrTimeout } } diff --git a/routing/dht/routing.go b/routing/dht/routing.go index cbe0e9246..138a0ee92 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -188,6 +188,12 @@ func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error if err != nil { return nil, err } - panic("Not yet implemented.") + addr := string(pmes_out.GetValue()) + maddr, err := ma.NewMultiaddr(addr) + if err != nil { + return nil, err + } + + return s.Connect(maddr) } } diff --git a/swarm/swarm.go b/swarm/swarm.go index 451de76ed..882c0a05d 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -163,6 +163,14 @@ func (s *Swarm) handleNewConn(nconn net.Conn) { panic(err) } + // Get address to contact remote peer from + addr := <-conn.Incoming.MsgChan + maddr, err := ma.NewMultiaddr(string(addr)) + if err != nil { + u.PErr("Got invalid address from peer.") + } + p.AddAddress(maddr) + s.StartConn(conn) } From bd9fc2b782192ff37c7d9e15d3ecc90a55ba4c66 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Wed, 6 Aug 2014 10:02:53 -0700 Subject: [PATCH 12/30] fix bug in routing table lookups --- routing/dht/dht.go | 14 +++++++++++++- routing/dht/dht_test.go | 4 ++-- routing/dht/messages.pb.go | 17 +++++++++-------- routing/dht/messages.proto | 1 + routing/dht/pDHTMessage.go | 11 ----------- routing/dht/routing.go | 13 ++++++++++++- routing/dht/table.go | 24 +++++++++++++++++++----- routing/dht/table_test.go | 17 +++++++++++++++++ swarm/swarm.go | 2 +- util/util.go | 9 +++++++-- 10 files changed, 81 insertions(+), 31 deletions(-) diff --git a/routing/dht/dht.go b/routing/dht/dht.go index c2c1b63be..9b4854cfe 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -106,6 +106,14 @@ func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { if removed != nil { panic("need to remove this peer.") } + + // Ping new peer to register in their routing table + // NOTE: this should be done better... + err = dht.Ping(peer, time.Second * 2) + if err != nil { + panic("Failed to ping new peer.") + } + return peer, nil } @@ -149,7 +157,7 @@ func (dht *IpfsDHT) handleMessages() { } // - u.DOut("Got message type: '%s' [id = %x]", mesNames[pmes.GetType()], pmes.GetId()) + u.DOut("Got message type: '%s' [id = %x]", DHTMessage_MessageType_name[int32(pmes.GetType())], pmes.GetId()) switch pmes.GetType() { case DHTMessage_GET_VALUE: dht.handleGetValue(mes.Peer, pmes) @@ -215,14 +223,18 @@ func (dht *IpfsDHT) handlePing(p *peer.Peer, pmes *DHTMessage) { } func (dht *IpfsDHT) handleFindPeer(p *peer.Peer, pmes *DHTMessage) { + u.POut("handleFindPeer: searching for '%s'", peer.ID(pmes.GetKey()).Pretty()) closest := dht.routes.NearestPeer(convertKey(u.Key(pmes.GetKey()))) if closest == nil { + panic("could not find anything.") } if len(closest.Addresses) == 0 { panic("no addresses for connected peer...") } + u.POut("handleFindPeer: sending back '%s'", closest.ID.Pretty()) + addr,err := closest.Addresses[0].String() if err != nil { panic(err) diff --git a/routing/dht/dht_test.go b/routing/dht/dht_test.go index 0c4b7ee09..6217c29f2 100644 --- a/routing/dht/dht_test.go +++ b/routing/dht/dht_test.go @@ -45,7 +45,7 @@ func TestPing(t *testing.T) { dht_a.Start() dht_b.Start() - err = dht_a.Connect(addr_b) + _,err = dht_a.Connect(addr_b) if err != nil { t.Fatal(err) } @@ -92,7 +92,7 @@ func TestValueGetSet(t *testing.T) { dht_a.Start() dht_b.Start() - err = dht_a.Connect(addr_b) + _,err = dht_a.Connect(addr_b) if err != nil { t.Fatal(err) } diff --git a/routing/dht/messages.pb.go b/routing/dht/messages.pb.go index 3283ef4e2..e95f487c1 100644 --- a/routing/dht/messages.pb.go +++ b/routing/dht/messages.pb.go @@ -29,6 +29,7 @@ const ( DHTMessage_GET_PROVIDERS DHTMessage_MessageType = 3 DHTMessage_FIND_NODE DHTMessage_MessageType = 4 DHTMessage_PING DHTMessage_MessageType = 5 + DHTMessage_DIAGNOSTIC DHTMessage_MessageType = 6 ) var DHTMessage_MessageType_name = map[int32]string{ @@ -38,6 +39,7 @@ var DHTMessage_MessageType_name = map[int32]string{ 3: "GET_PROVIDERS", 4: "FIND_NODE", 5: "PING", + 6: "DIAGNOSTIC", } var DHTMessage_MessageType_value = map[string]int32{ "PUT_VALUE": 0, @@ -46,6 +48,7 @@ var DHTMessage_MessageType_value = map[string]int32{ "GET_PROVIDERS": 3, "FIND_NODE": 4, "PING": 5, + "DIAGNOSTIC": 6, } func (x DHTMessage_MessageType) Enum() *DHTMessage_MessageType { @@ -66,14 +69,12 @@ func (x *DHTMessage_MessageType) UnmarshalJSON(data []byte) error { } type DHTMessage struct { - Type *DHTMessage_MessageType `protobuf:"varint,1,req,name=type,enum=dht.DHTMessage_MessageType" json:"type,omitempty"` - Key *string `protobuf:"bytes,2,opt,name=key" json:"key,omitempty"` - Value []byte `protobuf:"bytes,3,opt,name=value" json:"value,omitempty"` - // Unique ID of this message, used to match queries with responses - Id *uint64 `protobuf:"varint,4,req,name=id" json:"id,omitempty"` - // Signals whether or not this message is a response to another message - Response *bool `protobuf:"varint,5,opt,name=response" json:"response,omitempty"` - XXX_unrecognized []byte `json:"-"` + Type *DHTMessage_MessageType `protobuf:"varint,1,req,name=type,enum=dht.DHTMessage_MessageType" json:"type,omitempty"` + Key *string `protobuf:"bytes,2,opt,name=key" json:"key,omitempty"` + Value []byte `protobuf:"bytes,3,opt,name=value" json:"value,omitempty"` + Id *uint64 `protobuf:"varint,4,req,name=id" json:"id,omitempty"` + Response *bool `protobuf:"varint,5,opt,name=response" json:"response,omitempty"` + XXX_unrecognized []byte `json:"-"` } func (m *DHTMessage) Reset() { *m = DHTMessage{} } diff --git a/routing/dht/messages.proto b/routing/dht/messages.proto index d873c7559..a9a7fd3c6 100644 --- a/routing/dht/messages.proto +++ b/routing/dht/messages.proto @@ -10,6 +10,7 @@ message DHTMessage { GET_PROVIDERS = 3; FIND_NODE = 4; PING = 5; + DIAGNOSTIC = 6; } required MessageType type = 1; diff --git a/routing/dht/pDHTMessage.go b/routing/dht/pDHTMessage.go index 8b862dbc9..65c03b1f8 100644 --- a/routing/dht/pDHTMessage.go +++ b/routing/dht/pDHTMessage.go @@ -9,17 +9,6 @@ type pDHTMessage struct { Id uint64 } -var mesNames [10]string - -func init() { - mesNames[DHTMessage_ADD_PROVIDER] = "add provider" - mesNames[DHTMessage_FIND_NODE] = "find node" - mesNames[DHTMessage_GET_PROVIDERS] = "get providers" - mesNames[DHTMessage_GET_VALUE] = "get value" - mesNames[DHTMessage_PUT_VALUE] = "put value" - mesNames[DHTMessage_PING] = "ping" -} - func (m *pDHTMessage) ToProtobuf() *DHTMessage { pmes := new(DHTMessage) if m.Value != nil { diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 138a0ee92..489498350 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -194,6 +194,17 @@ func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error return nil, err } - return s.Connect(maddr) + found_peer, err := s.Connect(maddr) + if err != nil { + u.POut("Found peer but couldnt connect.") + return nil, err + } + + if !found_peer.ID.Equal(id) { + u.POut("FindPeer: searching for '%s' but found '%s'", id.Pretty(), found_peer.ID.Pretty()) + return found_peer, u.ErrSearchIncomplete + } + + return found_peer, nil } } diff --git a/routing/dht/table.go b/routing/dht/table.go index 17af95756..ce8fdbc24 100644 --- a/routing/dht/table.go +++ b/routing/dht/table.go @@ -1,10 +1,12 @@ package dht import ( + "encoding/hex" "container/list" "sort" peer "github.com/jbenet/go-ipfs/peer" + u "github.com/jbenet/go-ipfs/util" ) // RoutingTable defines the routing table. @@ -87,13 +89,13 @@ func (p peerSorterArr) Less(a, b int) bool { } // -func (rt *RoutingTable) copyPeersFromList(peerArr peerSorterArr, peerList *list.List) peerSorterArr { +func copyPeersFromList(target ID, peerArr peerSorterArr, peerList *list.List) peerSorterArr { for e := peerList.Front(); e != nil; e = e.Next() { p := e.Value.(*peer.Peer) p_id := convertPeerID(p.ID) pd := peerDistance{ p: p, - distance: xor(rt.local, p_id), + distance: xor(target, p_id), } peerArr = append(peerArr, &pd) } @@ -112,6 +114,7 @@ func (rt *RoutingTable) NearestPeer(id ID) *peer.Peer { // Returns a list of the 'count' closest peers to the given ID func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { + u.POut("Searching table, size = %d", rt.Size()) cpl := xor(id, rt.local).commonPrefixLen() // Get bucket at cpl index or last bucket @@ -127,16 +130,16 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { // if this happens, search both surrounding buckets for nearest peer if cpl > 0 { plist := (*list.List)(rt.Buckets[cpl - 1]) - peerArr = rt.copyPeersFromList(peerArr, plist) + peerArr = copyPeersFromList(id, peerArr, plist) } if cpl < len(rt.Buckets) - 1 { plist := (*list.List)(rt.Buckets[cpl + 1]) - peerArr = rt.copyPeersFromList(peerArr, plist) + peerArr = copyPeersFromList(id, peerArr, plist) } } else { plist := (*list.List)(bucket) - peerArr = rt.copyPeersFromList(peerArr, plist) + peerArr = copyPeersFromList(id, peerArr, plist) } // Sort by distance to local peer @@ -145,7 +148,18 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { var out []*peer.Peer for i := 0; i < count && i < peerArr.Len(); i++ { out = append(out, peerArr[i].p) + u.POut("peer out: %s - %s", peerArr[i].p.ID.Pretty(), + hex.EncodeToString(xor(id, convertPeerID(peerArr[i].p.ID)))) } return out } + +// Returns the total number of peers in the routing table +func (rt *RoutingTable) Size() int { + var tot int + for _,buck := range rt.Buckets { + tot += buck.Len() + } + return tot +} diff --git a/routing/dht/table_test.go b/routing/dht/table_test.go index cb52bd1a0..debec5e16 100644 --- a/routing/dht/table_test.go +++ b/routing/dht/table_test.go @@ -90,3 +90,20 @@ func TestTableUpdate(t *testing.T) { } } } + +func TestTableFind(t *testing.T) { + local := _randPeer() + rt := NewRoutingTable(10, convertPeerID(local.ID)) + + peers := make([]*peer.Peer, 100) + for i := 0; i < 5; i++ { + peers[i] = _randPeer() + rt.Update(peers[i]) + } + + t.Logf("Searching for peer: '%s'", peers[2].ID.Pretty()) + found := rt.NearestPeer(convertPeerID(peers[2].ID)) + if !found.ID.Equal(peers[2].ID) { + t.Fatalf("Failed to lookup known node...") + } +} diff --git a/swarm/swarm.go b/swarm/swarm.go index 882c0a05d..b286c3c82 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -245,7 +245,7 @@ func (s *Swarm) fanOut() { if !ok { return } - u.DOut("fanOut: outgoing message for: '%s'", msg.Peer.Key().Pretty()) + //u.DOut("fanOut: outgoing message for: '%s'", msg.Peer.Key().Pretty()) s.connsLock.RLock() conn, found := s.conns[msg.Peer.Key()] diff --git a/util/util.go b/util/util.go index f5eec56bb..ca9ab79d3 100644 --- a/util/util.go +++ b/util/util.go @@ -2,6 +2,7 @@ package util import ( "fmt" + "errors" mh "github.com/jbenet/go-multihash" "os" "os/user" @@ -13,10 +14,14 @@ import ( var Debug bool // ErrNotImplemented signifies a function has not been implemented yet. -var ErrNotImplemented = fmt.Errorf("Error: not implemented yet.") +var ErrNotImplemented = errors.New("Error: not implemented yet.") // ErrTimeout implies that a timeout has been triggered -var ErrTimeout = fmt.Errorf("Error: Call timed out.") +var ErrTimeout = errors.New("Error: Call timed out.") + +// ErrSeErrSearchIncomplete implies that a search type operation didnt +// find the expected node, but did find 'a' node. +var ErrSearchIncomplete = errors.New("Error: Search Incomplete.") // Key is a string representation of multihash for use with maps. type Key string From 41c124a2825e7b1860464f090089799e4b3863d8 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Wed, 6 Aug 2014 18:37:45 -0700 Subject: [PATCH 13/30] worked on gathering data for diagnostic messages and some other misc cleanup --- peer/peer.go | 2 + routing/dht/bucket.go | 5 +++ routing/dht/dht.go | 87 +++++++++++++++++++++----------------- routing/dht/dht_test.go | 4 -- routing/dht/messages.pb.go | 8 ++++ routing/dht/messages.proto | 1 + routing/dht/pDHTMessage.go | 2 + routing/dht/routing.go | 27 ++++++++++++ routing/dht/table.go | 16 ++++--- routing/dht/util.go | 4 +- 10 files changed, 107 insertions(+), 49 deletions(-) diff --git a/peer/peer.go b/peer/peer.go index 0357ab552..a637274c7 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -2,6 +2,7 @@ package peer import ( "encoding/hex" + "time" u "github.com/jbenet/go-ipfs/util" ma "github.com/jbenet/go-multiaddr" @@ -30,6 +31,7 @@ type Map map[u.Key]*Peer type Peer struct { ID ID Addresses []*ma.Multiaddr + Distance time.Duration } // Key returns the ID as a Key (string) for maps. diff --git a/routing/dht/bucket.go b/routing/dht/bucket.go index 120ed29a4..996d299d9 100644 --- a/routing/dht/bucket.go +++ b/routing/dht/bucket.go @@ -61,3 +61,8 @@ func (b *Bucket) Split(cpl int, target ID) *Bucket { } return (*Bucket)(out) } + +func (b *Bucket) getIter() *list.Element { + bucket_list := (*list.List)(b) + return bucket_list.Front() +} diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 9b4854cfe..44a56831b 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -34,7 +34,7 @@ type IpfsDHT struct { // Map keys to peers that can provide their value // TODO: implement a TTL on each of these keys - providers map[u.Key][]*peer.Peer + providers map[u.Key][]*providerInfo providerLock sync.RWMutex // map of channels waiting for reply messages @@ -43,6 +43,9 @@ type IpfsDHT struct { // Signal to shutdown dht shutdown chan struct{} + + // When this peer started up + birth time.Time } // Create a new DHT object with the given peer as the 'local' host @@ -61,9 +64,10 @@ func NewDHT(p *peer.Peer) (*IpfsDHT, error) { dht.datastore = ds.NewMapDatastore() dht.self = p dht.listeners = make(map[uint64]chan *swarm.Message) - dht.providers = make(map[u.Key][]*peer.Peer) + dht.providers = make(map[u.Key][]*providerInfo) dht.shutdown = make(chan struct{}) dht.routes = NewRoutingTable(20, convertPeerID(p.ID)) + dht.birth = time.Now() return dht, nil } @@ -121,6 +125,8 @@ func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { // NOTE: this function is just a quick sketch func (dht *IpfsDHT) handleMessages() { u.DOut("Begin message handling routine") + + checkTimeouts := time.NewTicker(time.Minute * 5) for { select { case mes,ok := <-dht.network.Chan.Incoming: @@ -157,7 +163,10 @@ func (dht *IpfsDHT) handleMessages() { } // - u.DOut("Got message type: '%s' [id = %x]", DHTMessage_MessageType_name[int32(pmes.GetType())], pmes.GetId()) + u.DOut("[peer: %s]", dht.self.ID.Pretty()) + u.DOut("Got message type: '%s' [id = %x, from = %s]", + DHTMessage_MessageType_name[int32(pmes.GetType())], + pmes.GetId(), mes.Peer.ID.Pretty()) switch pmes.GetType() { case DHTMessage_GET_VALUE: dht.handleGetValue(mes.Peer, pmes) @@ -171,35 +180,57 @@ func (dht *IpfsDHT) handleMessages() { dht.handleGetProviders(mes.Peer, pmes) case DHTMessage_PING: dht.handlePing(mes.Peer, pmes) + case DHTMessage_DIAGNOSTIC: + // TODO: network diagnostic messages } case err := <-dht.network.Chan.Errors: u.DErr("dht err: %s", err) case <-dht.shutdown: + checkTimeouts.Stop() return + case <-checkTimeouts.C: + dht.providerLock.Lock() + for k,parr := range dht.providers { + var cleaned []*providerInfo + for _,v := range parr { + if time.Since(v.Creation) < time.Hour { + cleaned = append(cleaned, v) + } + } + dht.providers[k] = cleaned + } + dht.providerLock.Unlock() } } } func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *DHTMessage) { dskey := ds.NewKey(pmes.GetKey()) + var resp *pDHTMessage i_val, err := dht.datastore.Get(dskey) if err == nil { - resp := &pDHTMessage{ + resp = &pDHTMessage{ Response: true, Id: *pmes.Id, Key: *pmes.Key, Value: i_val.([]byte), + Success: true, } - - mes := swarm.NewMessage(p, resp.ToProtobuf()) - dht.network.Chan.Outgoing <- mes } else if err == ds.ErrNotFound { // Find closest peer(s) to desired key and reply with that info - // TODO: this will need some other metadata in the protobuf message - // to signal to the querying peer that the data its receiving - // is actually a list of other peer + closer := dht.routes.NearestPeer(convertKey(u.Key(pmes.GetKey()))) + resp = &pDHTMessage{ + Response: true, + Id: *pmes.Id, + Key: *pmes.Key, + Value: closer.ID, + Success: false, + } } + + mes := swarm.NewMessage(p, resp.ToProtobuf()) + dht.network.Chan.Outgoing <- mes } // Store a value in this peer local storage @@ -263,14 +294,14 @@ func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *DHTMessage) { // This is just a quick hack, formalize method of sending addrs later addrs := make(map[u.Key]string) for _,prov := range providers { - ma := prov.NetAddress("tcp") + ma := prov.Value.NetAddress("tcp") str,err := ma.String() if err != nil { u.PErr("Error: %s", err) continue } - addrs[prov.Key()] = str + addrs[prov.Value.Key()] = str } data,err := json.Marshal(addrs) @@ -290,6 +321,11 @@ func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *DHTMessage) { dht.network.Chan.Outgoing <-mes } +type providerInfo struct { + Creation time.Time + Value *peer.Peer +} + func (dht *IpfsDHT) handleAddProvider(p *peer.Peer, pmes *DHTMessage) { //TODO: need to implement TTLs on providers key := u.Key(pmes.GetKey()) @@ -324,35 +360,10 @@ func (dht *IpfsDHT) Halt() { dht.network.Close() } -// Ping a peer, log the time it took -func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { - // Thoughts: maybe this should accept an ID and do a peer lookup? - u.DOut("Enter Ping.") - - pmes := pDHTMessage{Id: GenerateMessageID(), Type: DHTMessage_PING} - mes := swarm.NewMessage(p, pmes.ToProtobuf()) - - before := time.Now() - response_chan := dht.ListenFor(pmes.Id) - dht.network.Chan.Outgoing <- mes - - tout := time.After(timeout) - select { - case <-response_chan: - roundtrip := time.Since(before) - u.POut("Ping took %s.", roundtrip.String()) - return nil - case <-tout: - // Timed out, think about removing peer from network - u.DOut("Ping peer timed out.") - return u.ErrTimeout - } -} - func (dht *IpfsDHT) addProviderEntry(key u.Key, p *peer.Peer) { u.DOut("Adding %s as provider for '%s'", p.Key().Pretty(), key) dht.providerLock.Lock() provs := dht.providers[key] - dht.providers[key] = append(provs, p) + dht.providers[key] = append(provs, &providerInfo{time.Now(), p}) dht.providerLock.Unlock() } diff --git a/routing/dht/dht_test.go b/routing/dht/dht_test.go index 6217c29f2..b57ca3f7b 100644 --- a/routing/dht/dht_test.go +++ b/routing/dht/dht_test.go @@ -6,13 +6,9 @@ import ( ma "github.com/jbenet/go-multiaddr" u "github.com/jbenet/go-ipfs/util" - "fmt" - "time" ) -var _ = fmt.Println - func TestPing(t *testing.T) { u.Debug = false addr_a,err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/1234") diff --git a/routing/dht/messages.pb.go b/routing/dht/messages.pb.go index e95f487c1..4f427efa1 100644 --- a/routing/dht/messages.pb.go +++ b/routing/dht/messages.pb.go @@ -74,6 +74,7 @@ type DHTMessage struct { Value []byte `protobuf:"bytes,3,opt,name=value" json:"value,omitempty"` Id *uint64 `protobuf:"varint,4,req,name=id" json:"id,omitempty"` Response *bool `protobuf:"varint,5,opt,name=response" json:"response,omitempty"` + Success *bool `protobuf:"varint,6,opt,name=success" json:"success,omitempty"` XXX_unrecognized []byte `json:"-"` } @@ -116,6 +117,13 @@ func (m *DHTMessage) GetResponse() bool { return false } +func (m *DHTMessage) GetSuccess() bool { + if m != nil && m.Success != nil { + return *m.Success + } + return false +} + func init() { proto.RegisterEnum("dht.DHTMessage_MessageType", DHTMessage_MessageType_name, DHTMessage_MessageType_value) } diff --git a/routing/dht/messages.proto b/routing/dht/messages.proto index a9a7fd3c6..278a95202 100644 --- a/routing/dht/messages.proto +++ b/routing/dht/messages.proto @@ -22,4 +22,5 @@ message DHTMessage { // Signals whether or not this message is a response to another message optional bool response = 5; + optional bool success = 6; } diff --git a/routing/dht/pDHTMessage.go b/routing/dht/pDHTMessage.go index 65c03b1f8..bfe37d35f 100644 --- a/routing/dht/pDHTMessage.go +++ b/routing/dht/pDHTMessage.go @@ -7,6 +7,7 @@ type pDHTMessage struct { Value []byte Response bool Id uint64 + Success bool } func (m *pDHTMessage) ToProtobuf() *DHTMessage { @@ -19,6 +20,7 @@ func (m *pDHTMessage) ToProtobuf() *DHTMessage { pmes.Key = &m.Key pmes.Response = &m.Response pmes.Id = &m.Id + pmes.Success = &m.Success return pmes } diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 489498350..82c88960d 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -208,3 +208,30 @@ func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error return found_peer, nil } } + +// Ping a peer, log the time it took +func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { + // Thoughts: maybe this should accept an ID and do a peer lookup? + u.DOut("Enter Ping.") + + pmes := pDHTMessage{Id: GenerateMessageID(), Type: DHTMessage_PING} + mes := swarm.NewMessage(p, pmes.ToProtobuf()) + + before := time.Now() + response_chan := dht.ListenFor(pmes.Id) + dht.network.Chan.Outgoing <- mes + + tout := time.After(timeout) + select { + case <-response_chan: + roundtrip := time.Since(before) + p.Distance = roundtrip //TODO: This isnt threadsafe + u.POut("Ping took %s.", roundtrip.String()) + return nil + case <-tout: + // Timed out, think about removing peer from network + u.DOut("Ping peer timed out.") + dht.Unlisten(pmes.Id) + return u.ErrTimeout + } +} diff --git a/routing/dht/table.go b/routing/dht/table.go index ce8fdbc24..07ed70cb4 100644 --- a/routing/dht/table.go +++ b/routing/dht/table.go @@ -1,12 +1,10 @@ package dht import ( - "encoding/hex" "container/list" "sort" peer "github.com/jbenet/go-ipfs/peer" - u "github.com/jbenet/go-ipfs/util" ) // RoutingTable defines the routing table. @@ -114,7 +112,6 @@ func (rt *RoutingTable) NearestPeer(id ID) *peer.Peer { // Returns a list of the 'count' closest peers to the given ID func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { - u.POut("Searching table, size = %d", rt.Size()) cpl := xor(id, rt.local).commonPrefixLen() // Get bucket at cpl index or last bucket @@ -148,8 +145,6 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { var out []*peer.Peer for i := 0; i < count && i < peerArr.Len(); i++ { out = append(out, peerArr[i].p) - u.POut("peer out: %s - %s", peerArr[i].p.ID.Pretty(), - hex.EncodeToString(xor(id, convertPeerID(peerArr[i].p.ID)))) } return out @@ -163,3 +158,14 @@ func (rt *RoutingTable) Size() int { } return tot } + +// NOTE: This is potentially unsafe... use at your own risk +func (rt *RoutingTable) listpeers() []*peer.Peer { + var peers []*peer.Peer + for _,buck := range rt.Buckets { + for e := buck.getIter(); e != nil; e = e.Next() { + peers = append(peers, e.Value.(*peer.Peer)) + } + } + return peers +} diff --git a/routing/dht/util.go b/routing/dht/util.go index eed8d9301..2adc8b765 100644 --- a/routing/dht/util.go +++ b/routing/dht/util.go @@ -11,8 +11,8 @@ import ( // ID for IpfsDHT should be a byte slice, to allow for simpler operations // (xor). DHT ids are based on the peer.IDs. // -// NOTE: peer.IDs are biased because they are multihashes (first bytes -// biased). Thus, may need to re-hash keys (uniform dist). TODO(jbenet) +// The type dht.ID signifies that its contents have been hashed from either a +// peer.ID or a util.Key. This unifies the keyspace type ID []byte func (id ID) Equal(other ID) bool { From c22b6aa316c89915ea95219584a46470b80b7f71 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Wed, 6 Aug 2014 21:36:56 -0700 Subject: [PATCH 14/30] fixing some race conditions --- peer/peer.go | 15 ++++++++++++++- routing/dht/dht.go | 1 + routing/dht/routing.go | 10 ++++++++-- routing/dht/table.go | 20 +++++++++++++++++++- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/peer/peer.go b/peer/peer.go index a637274c7..8b6ff4d90 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -3,6 +3,7 @@ package peer import ( "encoding/hex" "time" + "sync" u "github.com/jbenet/go-ipfs/util" ma "github.com/jbenet/go-multiaddr" @@ -31,7 +32,9 @@ type Map map[u.Key]*Peer type Peer struct { ID ID Addresses []*ma.Multiaddr - Distance time.Duration + + distance time.Duration + distLock sync.RWMutex } // Key returns the ID as a Key (string) for maps. @@ -60,3 +63,13 @@ func (p *Peer) NetAddress(n string) *ma.Multiaddr { } return nil } + +func (p *Peer) GetDistance() time.Duration { + return p.distance +} + +func (p *Peer) SetDistance(dist time.Duration) { + p.distLock.Lock() + p.distance = dist + p.distLock.Unlock() +} diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 44a56831b..19dfac99c 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -186,6 +186,7 @@ func (dht *IpfsDHT) handleMessages() { case err := <-dht.network.Chan.Errors: u.DErr("dht err: %s", err) + panic(err) case <-dht.shutdown: checkTimeouts.Stop() return diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 82c88960d..22a7cf886 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -48,6 +48,8 @@ func (s *IpfsDHT) PutValue(key u.Key, value []byte) error { } // GetValue searches for the value corresponding to given Key. +// If the search does not succeed, a multiaddr string of a closer peer is +// returned along with util.ErrSearchIncomplete func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { var p *peer.Peer p = s.routes.NearestPeer(convertKey(key)) @@ -77,7 +79,11 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { if err != nil { return nil,err } - return pmes_out.GetValue(), nil + if pmes_out.GetSuccess() { + return pmes_out.GetValue(), nil + } else { + return pmes_out.GetValue(), u.ErrSearchIncomplete + } } } @@ -225,7 +231,7 @@ func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { select { case <-response_chan: roundtrip := time.Since(before) - p.Distance = roundtrip //TODO: This isnt threadsafe + p.SetDistance(roundtrip) u.POut("Ping took %s.", roundtrip.String()) return nil case <-tout: diff --git a/routing/dht/table.go b/routing/dht/table.go index 07ed70cb4..b4be9c321 100644 --- a/routing/dht/table.go +++ b/routing/dht/table.go @@ -3,8 +3,10 @@ package dht import ( "container/list" "sort" + "sync" peer "github.com/jbenet/go-ipfs/peer" + u "github.com/jbenet/go-ipfs/util" ) // RoutingTable defines the routing table. @@ -13,6 +15,9 @@ type RoutingTable struct { // ID of the local peer local ID + // Blanket lock, refine later for better performance + tabLock sync.RWMutex + // kBuckets define all the fingers to other nodes. Buckets []*Bucket bucketsize int @@ -29,6 +34,8 @@ func NewRoutingTable(bucketsize int, local_id ID) *RoutingTable { // Update adds or moves the given peer to the front of its respective bucket // If a peer gets removed from a bucket, it is returned func (rt *RoutingTable) Update(p *peer.Peer) *peer.Peer { + rt.tabLock.Lock() + defer rt.tabLock.Unlock() peer_id := convertPeerID(p.ID) cpl := xor(peer_id, rt.local).commonPrefixLen() @@ -88,7 +95,11 @@ func (p peerSorterArr) Less(a, b int) bool { // func copyPeersFromList(target ID, peerArr peerSorterArr, peerList *list.List) peerSorterArr { - for e := peerList.Front(); e != nil; e = e.Next() { + if peerList == nil { + return peerSorterArr{} + } + e := peerList.Front() + for ; e != nil; { p := e.Value.(*peer.Peer) p_id := convertPeerID(p.ID) pd := peerDistance{ @@ -96,6 +107,11 @@ func copyPeersFromList(target ID, peerArr peerSorterArr, peerList *list.List) pe distance: xor(target, p_id), } peerArr = append(peerArr, &pd) + if e != nil { + u.POut("list element was nil.") + return peerArr + } + e = e.Next() } return peerArr } @@ -112,6 +128,8 @@ func (rt *RoutingTable) NearestPeer(id ID) *peer.Peer { // Returns a list of the 'count' closest peers to the given ID func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { + rt.tabLock.RLock() + defer rt.tabLock.RUnlock() cpl := xor(id, rt.local).commonPrefixLen() // Get bucket at cpl index or last bucket From 01ca93b4f5adfefe1348a51d1860011a4c0c02f4 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Thu, 7 Aug 2014 14:16:24 -0700 Subject: [PATCH 15/30] fixed small bug introduced during race condition frustration --- routing/dht/dht.go | 100 ++++++++++++++++++++++++++++++++++------- routing/dht/routing.go | 71 ++++++++++++++++++++++++----- routing/dht/table.go | 9 +--- 3 files changed, 148 insertions(+), 32 deletions(-) diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 19dfac99c..5791c3fed 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -3,6 +3,7 @@ package dht import ( "sync" "time" + "bytes" "encoding/json" peer "github.com/jbenet/go-ipfs/peer" @@ -22,7 +23,7 @@ import ( // IpfsDHT is an implementation of Kademlia with Coral and S/Kademlia modifications. // It is used to implement the base IpfsRouting module. type IpfsDHT struct { - routes *RoutingTable + routes []*RoutingTable network *swarm.Swarm @@ -38,7 +39,7 @@ type IpfsDHT struct { providerLock sync.RWMutex // map of channels waiting for reply messages - listeners map[uint64]chan *swarm.Message + listeners map[uint64]*listenInfo listenLock sync.RWMutex // Signal to shutdown dht @@ -46,6 +47,14 @@ type IpfsDHT struct { // When this peer started up birth time.Time + + //lock to make diagnostics work better + diaglock sync.Mutex +} + +type listenInfo struct { + resp chan *swarm.Message + count int } // Create a new DHT object with the given peer as the 'local' host @@ -63,10 +72,11 @@ func NewDHT(p *peer.Peer) (*IpfsDHT, error) { dht.network = network dht.datastore = ds.NewMapDatastore() dht.self = p - dht.listeners = make(map[uint64]chan *swarm.Message) + dht.listeners = make(map[uint64]*listenInfo) dht.providers = make(map[u.Key][]*providerInfo) dht.shutdown = make(chan struct{}) - dht.routes = NewRoutingTable(20, convertPeerID(p.ID)) + dht.routes = make([]*RoutingTable, 1) + dht.routes[0] = NewRoutingTable(20, convertPeerID(p.ID)) dht.birth = time.Now() return dht, nil } @@ -106,7 +116,7 @@ func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { dht.network.StartConn(conn) - removed := dht.routes.Update(peer) + removed := dht.routes[0].Update(peer) if removed != nil { panic("need to remove this peer.") } @@ -142,7 +152,7 @@ func (dht *IpfsDHT) handleMessages() { } // Update peers latest visit in routing table - removed := dht.routes.Update(mes.Peer) + removed := dht.routes[0].Update(mes.Peer) if removed != nil { panic("Need to handle removed peer.") } @@ -150,10 +160,15 @@ func (dht *IpfsDHT) handleMessages() { // Note: not sure if this is the correct place for this if pmes.GetResponse() { dht.listenLock.RLock() - ch, ok := dht.listeners[pmes.GetId()] + list, ok := dht.listeners[pmes.GetId()] + if list.count > 1 { + list.count-- + } else if list.count == 1 { + delete(dht.listeners, pmes.GetId()) + } dht.listenLock.RUnlock() if ok { - ch <- mes + list.resp <- mes } else { // this is expected behaviour during a timeout u.DOut("Received response with nobody listening...") @@ -181,7 +196,7 @@ func (dht *IpfsDHT) handleMessages() { case DHTMessage_PING: dht.handlePing(mes.Peer, pmes) case DHTMessage_DIAGNOSTIC: - // TODO: network diagnostic messages + dht.handleDiagnostic(mes.Peer, pmes) } case err := <-dht.network.Chan.Errors: @@ -220,7 +235,7 @@ func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *DHTMessage) { } } else if err == ds.ErrNotFound { // Find closest peer(s) to desired key and reply with that info - closer := dht.routes.NearestPeer(convertKey(u.Key(pmes.GetKey()))) + closer := dht.routes[0].NearestPeer(convertKey(u.Key(pmes.GetKey()))) resp = &pDHTMessage{ Response: true, Id: *pmes.Id, @@ -256,7 +271,7 @@ func (dht *IpfsDHT) handlePing(p *peer.Peer, pmes *DHTMessage) { func (dht *IpfsDHT) handleFindPeer(p *peer.Peer, pmes *DHTMessage) { u.POut("handleFindPeer: searching for '%s'", peer.ID(pmes.GetKey()).Pretty()) - closest := dht.routes.NearestPeer(convertKey(u.Key(pmes.GetKey()))) + closest := dht.routes[0].NearestPeer(convertKey(u.Key(pmes.GetKey()))) if closest == nil { panic("could not find anything.") } @@ -336,10 +351,10 @@ func (dht *IpfsDHT) handleAddProvider(p *peer.Peer, pmes *DHTMessage) { // Register a handler for a specific message ID, used for getting replies // to certain messages (i.e. response to a GET_VALUE message) -func (dht *IpfsDHT) ListenFor(mesid uint64) <-chan *swarm.Message { +func (dht *IpfsDHT) ListenFor(mesid uint64, count int) <-chan *swarm.Message { lchan := make(chan *swarm.Message) dht.listenLock.Lock() - dht.listeners[mesid] = lchan + dht.listeners[mesid] = &listenInfo{lchan, count} dht.listenLock.Unlock() return lchan } @@ -347,12 +362,19 @@ func (dht *IpfsDHT) ListenFor(mesid uint64) <-chan *swarm.Message { // Unregister the given message id from the listener map func (dht *IpfsDHT) Unlisten(mesid uint64) { dht.listenLock.Lock() - ch, ok := dht.listeners[mesid] + list, ok := dht.listeners[mesid] if ok { delete(dht.listeners, mesid) } dht.listenLock.Unlock() - close(ch) + close(list.resp) +} + +func (dht *IpfsDHT) IsListening(mesid uint64) bool { + dht.listenLock.RLock() + _,ok := dht.listeners[mesid] + dht.listenLock.RUnlock() + return ok } // Stop all communications from this peer and shut down @@ -368,3 +390,51 @@ func (dht *IpfsDHT) addProviderEntry(key u.Key, p *peer.Peer) { dht.providers[key] = append(provs, &providerInfo{time.Now(), p}) dht.providerLock.Unlock() } + +func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *DHTMessage) { + dht.diaglock.Lock() + if dht.IsListening(pmes.GetId()) { + //TODO: ehhh.......... + dht.diaglock.Unlock() + return + } + dht.diaglock.Unlock() + + seq := dht.routes[0].NearestPeers(convertPeerID(dht.self.ID), 10) + listen_chan := dht.ListenFor(pmes.GetId(), len(seq)) + + for _,ps := range seq { + mes := swarm.NewMessage(ps, pmes) + dht.network.Chan.Outgoing <-mes + } + + + + buf := new(bytes.Buffer) + // NOTE: this shouldnt be a hardcoded value + after := time.After(time.Second * 20) + count := len(seq) + for count > 0 { + select { + case <-after: + //Timeout, return what we have + goto out + case req_resp := <-listen_chan: + buf.Write(req_resp.Data) + count-- + } + } + +out: + di := dht.getDiagInfo() + buf.Write(di.Marshal()) + resp := pDHTMessage{ + Type: DHTMessage_DIAGNOSTIC, + Id: pmes.GetId(), + Value: buf.Bytes(), + Response: true, + } + + mes := swarm.NewMessage(p, resp.ToProtobuf()) + dht.network.Chan.Outgoing <-mes +} diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 22a7cf886..8d45a4a43 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -3,6 +3,7 @@ package dht import ( "math/rand" "time" + "bytes" "encoding/json" proto "code.google.com/p/goprotobuf/proto" @@ -30,7 +31,7 @@ func GenerateMessageID() uint64 { // PutValue adds value corresponding to given Key. func (s *IpfsDHT) PutValue(key u.Key, value []byte) error { var p *peer.Peer - p = s.routes.NearestPeer(convertKey(key)) + p = s.routes[0].NearestPeer(convertKey(key)) if p == nil { panic("Table returned nil peer!") } @@ -52,7 +53,7 @@ func (s *IpfsDHT) PutValue(key u.Key, value []byte) error { // returned along with util.ErrSearchIncomplete func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { var p *peer.Peer - p = s.routes.NearestPeer(convertKey(key)) + p = s.routes[0].NearestPeer(convertKey(key)) if p == nil { panic("Table returned nil peer!") } @@ -62,7 +63,7 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { Key: string(key), Id: GenerateMessageID(), } - response_chan := s.ListenFor(pmes.Id) + response_chan := s.ListenFor(pmes.Id, 1) mes := swarm.NewMessage(p, pmes.ToProtobuf()) s.network.Chan.Outgoing <- mes @@ -92,7 +93,7 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { // Announce that this node can provide value for given key func (s *IpfsDHT) Provide(key u.Key) error { - peers := s.routes.NearestPeers(convertKey(key), PoolSize) + peers := s.routes[0].NearestPeers(convertKey(key), PoolSize) if len(peers) == 0 { //return an error } @@ -112,7 +113,7 @@ func (s *IpfsDHT) Provide(key u.Key) error { // FindProviders searches for peers who can provide the value for given key. func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, error) { - p := s.routes.NearestPeer(convertKey(key)) + p := s.routes[0].NearestPeer(convertKey(key)) pmes := pDHTMessage{ Type: DHTMessage_GET_PROVIDERS, @@ -122,7 +123,7 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, mes := swarm.NewMessage(p, pmes.ToProtobuf()) - listen_chan := s.ListenFor(pmes.Id) + listen_chan := s.ListenFor(pmes.Id, 1) u.DOut("Find providers for: '%s'", key) s.network.Chan.Outgoing <-mes after := time.After(timeout) @@ -163,7 +164,6 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, } return prov_arr, nil - } } @@ -171,7 +171,7 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, // FindPeer searches for a peer with given ID. func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error) { - p := s.routes.NearestPeer(convertPeerID(id)) + p := s.routes[0].NearestPeer(convertPeerID(id)) pmes := pDHTMessage{ Type: DHTMessage_FIND_NODE, @@ -181,7 +181,7 @@ func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error mes := swarm.NewMessage(p, pmes.ToProtobuf()) - listen_chan := s.ListenFor(pmes.Id) + listen_chan := s.ListenFor(pmes.Id, 1) s.network.Chan.Outgoing <-mes after := time.After(timeout) select { @@ -224,7 +224,7 @@ func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { mes := swarm.NewMessage(p, pmes.ToProtobuf()) before := time.Now() - response_chan := dht.ListenFor(pmes.Id) + response_chan := dht.ListenFor(pmes.Id, 1) dht.network.Chan.Outgoing <- mes tout := time.After(timeout) @@ -241,3 +241,54 @@ func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { return u.ErrTimeout } } + +func (dht *IpfsDHT) GetDiagnostic(timeout time.Duration) ([]*diagInfo, error) { + u.DOut("Begin Diagnostic") + //Send to N closest peers + targets := dht.routes[0].NearestPeers(convertPeerID(dht.self.ID), 10) + + // TODO: Add timeout to this struct so nodes know when to return + pmes := pDHTMessage{ + Type: DHTMessage_DIAGNOSTIC, + Id: GenerateMessageID(), + } + + listen_chan := dht.ListenFor(pmes.Id, len(targets)) + + pbmes := pmes.ToProtobuf() + for _,p := range targets { + mes := swarm.NewMessage(p, pbmes) + dht.network.Chan.Outgoing <-mes + } + + var out []*diagInfo + after := time.After(timeout) + for count := len(targets); count > 0; { + select { + case <-after: + u.DOut("Diagnostic request timed out.") + return out, u.ErrTimeout + case resp := <-listen_chan: + pmes_out := new(DHTMessage) + err := proto.Unmarshal(resp.Data, pmes_out) + if err != nil { + // NOTE: here and elsewhere, need to audit error handling, + // some errors should be continued on from + return out, err + } + + dec := json.NewDecoder(bytes.NewBuffer(pmes_out.GetValue())) + for { + di := new(diagInfo) + err := dec.Decode(di) + if err != nil { + break + } + + out = append(out, di) + } + } + } + + return nil,nil +} diff --git a/routing/dht/table.go b/routing/dht/table.go index b4be9c321..be4a4b392 100644 --- a/routing/dht/table.go +++ b/routing/dht/table.go @@ -95,11 +95,7 @@ func (p peerSorterArr) Less(a, b int) bool { // func copyPeersFromList(target ID, peerArr peerSorterArr, peerList *list.List) peerSorterArr { - if peerList == nil { - return peerSorterArr{} - } - e := peerList.Front() - for ; e != nil; { + for e := peerList.Front(); e != nil; e = e.Next() { p := e.Value.(*peer.Peer) p_id := convertPeerID(p.ID) pd := peerDistance{ @@ -107,11 +103,10 @@ func copyPeersFromList(target ID, peerArr peerSorterArr, peerList *list.List) pe distance: xor(target, p_id), } peerArr = append(peerArr, &pd) - if e != nil { + if e == nil { u.POut("list element was nil.") return peerArr } - e = e.Next() } return peerArr } From 24bfbfe37252b0f312f90428ecdebd4e6855384a Mon Sep 17 00:00:00 2001 From: Jeromy Date: Thu, 7 Aug 2014 18:06:50 -0700 Subject: [PATCH 16/30] implement timeouts on listeners for the dht and add diagnostic stuff --- routing/dht/dht.go | 64 +++++++++++++++++++++++++++++++++------ routing/dht/diag.go | 44 +++++++++++++++++++++++++++ routing/dht/routing.go | 30 +++++++++--------- routing/dht/table_test.go | 17 +++++++++++ 4 files changed, 130 insertions(+), 25 deletions(-) create mode 100644 routing/dht/diag.go diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 5791c3fed..63a368b32 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -23,6 +23,8 @@ import ( // IpfsDHT is an implementation of Kademlia with Coral and S/Kademlia modifications. // It is used to implement the base IpfsRouting module. type IpfsDHT struct { + // Array of routing tables for differently distanced nodes + // NOTE: (currently, only a single table is used) routes []*RoutingTable network *swarm.Swarm @@ -55,6 +57,7 @@ type IpfsDHT struct { type listenInfo struct { resp chan *swarm.Message count int + eol time.Time } // Create a new DHT object with the given peer as the 'local' host @@ -161,14 +164,19 @@ func (dht *IpfsDHT) handleMessages() { if pmes.GetResponse() { dht.listenLock.RLock() list, ok := dht.listeners[pmes.GetId()] + dht.listenLock.RUnlock() + if time.Now().After(list.eol) { + dht.Unlisten(pmes.GetId()) + ok = false + } if list.count > 1 { list.count-- - } else if list.count == 1 { - delete(dht.listeners, pmes.GetId()) } - dht.listenLock.RUnlock() if ok { list.resp <- mes + if list.count == 1 { + dht.Unlisten(pmes.GetId()) + } } else { // this is expected behaviour during a timeout u.DOut("Received response with nobody listening...") @@ -217,10 +225,35 @@ func (dht *IpfsDHT) handleMessages() { dht.providers[k] = cleaned } dht.providerLock.Unlock() + dht.listenLock.Lock() + var remove []uint64 + now := time.Now() + for k,v := range dht.listeners { + if now.After(v.eol) { + remove = append(remove, k) + } + } + for _,k := range remove { + delete(dht.listeners, k) + } + dht.listenLock.Unlock() } } } +func (dht *IpfsDHT) putValueToPeer(p *peer.Peer, key string, value []byte) error { + pmes := pDHTMessage{ + Type: DHTMessage_PUT_VALUE, + Key: key, + Value: value, + Id: GenerateMessageID(), + } + + mes := swarm.NewMessage(p, pmes.ToProtobuf()) + dht.network.Chan.Outgoing <- mes + return nil +} + func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *DHTMessage) { dskey := ds.NewKey(pmes.GetKey()) var resp *pDHTMessage @@ -351,10 +384,10 @@ func (dht *IpfsDHT) handleAddProvider(p *peer.Peer, pmes *DHTMessage) { // Register a handler for a specific message ID, used for getting replies // to certain messages (i.e. response to a GET_VALUE message) -func (dht *IpfsDHT) ListenFor(mesid uint64, count int) <-chan *swarm.Message { +func (dht *IpfsDHT) ListenFor(mesid uint64, count int, timeout time.Duration) <-chan *swarm.Message { lchan := make(chan *swarm.Message) dht.listenLock.Lock() - dht.listeners[mesid] = &listenInfo{lchan, count} + dht.listeners[mesid] = &listenInfo{lchan, count, time.Now().Add(timeout)} dht.listenLock.Unlock() return lchan } @@ -372,8 +405,14 @@ func (dht *IpfsDHT) Unlisten(mesid uint64) { func (dht *IpfsDHT) IsListening(mesid uint64) bool { dht.listenLock.RLock() - _,ok := dht.listeners[mesid] + li,ok := dht.listeners[mesid] dht.listenLock.RUnlock() + if time.Now().After(li.eol) { + dht.listenLock.Lock() + delete(dht.listeners, mesid) + dht.listenLock.Unlock() + return false + } return ok } @@ -401,7 +440,7 @@ func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *DHTMessage) { dht.diaglock.Unlock() seq := dht.routes[0].NearestPeers(convertPeerID(dht.self.ID), 10) - listen_chan := dht.ListenFor(pmes.GetId(), len(seq)) + listen_chan := dht.ListenFor(pmes.GetId(), len(seq), time.Second * 30) for _,ps := range seq { mes := swarm.NewMessage(ps, pmes) @@ -411,6 +450,9 @@ func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *DHTMessage) { buf := new(bytes.Buffer) + di := dht.getDiagInfo() + buf.Write(di.Marshal()) + // NOTE: this shouldnt be a hardcoded value after := time.After(time.Second * 20) count := len(seq) @@ -420,14 +462,18 @@ func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *DHTMessage) { //Timeout, return what we have goto out case req_resp := <-listen_chan: + pmes_out := new(DHTMessage) + err := proto.Unmarshal(req_resp.Data, pmes_out) + if err != nil { + // It broke? eh, whatever, keep going + continue + } buf.Write(req_resp.Data) count-- } } out: - di := dht.getDiagInfo() - buf.Write(di.Marshal()) resp := pDHTMessage{ Type: DHTMessage_DIAGNOSTIC, Id: pmes.GetId(), diff --git a/routing/dht/diag.go b/routing/dht/diag.go new file mode 100644 index 000000000..b8f211e40 --- /dev/null +++ b/routing/dht/diag.go @@ -0,0 +1,44 @@ +package dht + +import ( + "encoding/json" + "time" + + peer "github.com/jbenet/go-ipfs/peer" +) + +type connDiagInfo struct { + Latency time.Duration + Id peer.ID +} + +type diagInfo struct { + Id peer.ID + Connections []connDiagInfo + Keys []string + LifeSpan time.Duration + CodeVersion string +} + +func (di *diagInfo) Marshal() []byte { + b, err := json.Marshal(di) + if err != nil { + panic(err) + } + //TODO: also consider compressing this. There will be a lot of these + return b +} + + +func (dht *IpfsDHT) getDiagInfo() *diagInfo { + di := new(diagInfo) + di.CodeVersion = "github.com/jbenet/go-ipfs" + di.Id = dht.self.ID + di.LifeSpan = time.Since(dht.birth) + di.Keys = nil // Currently no way to query datastore + + for _,p := range dht.routes[0].listpeers() { + di.Connections = append(di.Connections, connDiagInfo{p.GetDistance(), p.ID}) + } + return di +} diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 8d45a4a43..1a90ce76b 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -29,6 +29,7 @@ func GenerateMessageID() uint64 { // Basic Put/Get // PutValue adds value corresponding to given Key. +// This is the top level "Store" operation of the DHT func (s *IpfsDHT) PutValue(key u.Key, value []byte) error { var p *peer.Peer p = s.routes[0].NearestPeer(convertKey(key)) @@ -36,16 +37,7 @@ func (s *IpfsDHT) PutValue(key u.Key, value []byte) error { panic("Table returned nil peer!") } - pmes := pDHTMessage{ - Type: DHTMessage_PUT_VALUE, - Key: string(key), - Value: value, - Id: GenerateMessageID(), - } - - mes := swarm.NewMessage(p, pmes.ToProtobuf()) - s.network.Chan.Outgoing <- mes - return nil + return s.putValueToPeer(p, string(key), value) } // GetValue searches for the value corresponding to given Key. @@ -63,7 +55,7 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { Key: string(key), Id: GenerateMessageID(), } - response_chan := s.ListenFor(pmes.Id, 1) + response_chan := s.ListenFor(pmes.Id, 1, time.Minute) mes := swarm.NewMessage(p, pmes.ToProtobuf()) s.network.Chan.Outgoing <- mes @@ -74,7 +66,13 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { case <-timeup: s.Unlisten(pmes.Id) return nil, u.ErrTimeout - case resp := <-response_chan: + case resp, ok := <-response_chan: + if !ok { + panic("Channel was closed...") + } + if resp == nil { + panic("Why the hell is this response nil?") + } pmes_out := new(DHTMessage) err := proto.Unmarshal(resp.Data, pmes_out) if err != nil { @@ -123,7 +121,7 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, mes := swarm.NewMessage(p, pmes.ToProtobuf()) - listen_chan := s.ListenFor(pmes.Id, 1) + listen_chan := s.ListenFor(pmes.Id, 1, time.Minute) u.DOut("Find providers for: '%s'", key) s.network.Chan.Outgoing <-mes after := time.After(timeout) @@ -181,7 +179,7 @@ func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error mes := swarm.NewMessage(p, pmes.ToProtobuf()) - listen_chan := s.ListenFor(pmes.Id, 1) + listen_chan := s.ListenFor(pmes.Id, 1, time.Minute) s.network.Chan.Outgoing <-mes after := time.After(timeout) select { @@ -224,7 +222,7 @@ func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { mes := swarm.NewMessage(p, pmes.ToProtobuf()) before := time.Now() - response_chan := dht.ListenFor(pmes.Id, 1) + response_chan := dht.ListenFor(pmes.Id, 1, time.Minute) dht.network.Chan.Outgoing <- mes tout := time.After(timeout) @@ -253,7 +251,7 @@ func (dht *IpfsDHT) GetDiagnostic(timeout time.Duration) ([]*diagInfo, error) { Id: GenerateMessageID(), } - listen_chan := dht.ListenFor(pmes.Id, len(targets)) + listen_chan := dht.ListenFor(pmes.Id, len(targets), time.Minute * 2) pbmes := pmes.ToProtobuf() for _,p := range targets { diff --git a/routing/dht/table_test.go b/routing/dht/table_test.go index debec5e16..393a1c585 100644 --- a/routing/dht/table_test.go +++ b/routing/dht/table_test.go @@ -107,3 +107,20 @@ func TestTableFind(t *testing.T) { t.Fatalf("Failed to lookup known node...") } } + +func TestTableFindMultiple(t *testing.T) { + local := _randPeer() + rt := NewRoutingTable(20, convertPeerID(local.ID)) + + peers := make([]*peer.Peer, 100) + for i := 0; i < 18; i++ { + peers[i] = _randPeer() + rt.Update(peers[i]) + } + + t.Logf("Searching for peer: '%s'", peers[2].ID.Pretty()) + found := rt.NearestPeers(convertPeerID(peers[2].ID), 15) + if len(found) != 15 { + t.Fatalf("Got back different number of peers than we expected.") + } +} From e14fb5658ea87bfab1e0fe0343f79b9793222f04 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Thu, 7 Aug 2014 21:52:11 -0700 Subject: [PATCH 17/30] add a unit test for provides functionality --- routing/dht/dht.go | 14 +++++++++ routing/dht/dht_test.go | 70 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 63a368b32..47f93e65f 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -92,6 +92,8 @@ func (dht *IpfsDHT) Start() { // Connect to a new peer at the given address // TODO: move this into swarm func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { + maddrstr,_ := addr.String() + u.DOut("Connect to new peer: %s", maddrstr) if addr == nil { panic("addr was nil!") } @@ -484,3 +486,15 @@ out: mes := swarm.NewMessage(p, resp.ToProtobuf()) dht.network.Chan.Outgoing <-mes } + +func (dht *IpfsDHT) GetLocal(key u.Key) ([]byte, error) { + v,err := dht.datastore.Get(ds.NewKey(string(key))) + if err != nil { + return nil, err + } + return v.([]byte), nil +} + +func (dht *IpfsDHT) PutLocal(key u.Key, value []byte) error { + return dht.datastore.Put(ds.NewKey(string(key)), value) +} diff --git a/routing/dht/dht_test.go b/routing/dht/dht_test.go index b57ca3f7b..2de027a3d 100644 --- a/routing/dht/dht_test.go +++ b/routing/dht/dht_test.go @@ -7,6 +7,7 @@ import ( u "github.com/jbenet/go-ipfs/util" "time" + "fmt" ) func TestPing(t *testing.T) { @@ -107,3 +108,72 @@ func TestValueGetSet(t *testing.T) { t.Fatalf("Expected 'world' got %s", string(val)) } } + +func TestProvides(t *testing.T) { + u.Debug = false + var addrs []*ma.Multiaddr + for i := 0; i < 4; i++ { + a,err := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 5000 + i)) + if err != nil { + t.Fatal(err) + } + addrs = append(addrs, a) + } + + + var peers []*peer.Peer + for i := 0; i < 4; i++ { + p := new(peer.Peer) + p.AddAddress(addrs[i]) + p.ID = peer.ID([]byte(fmt.Sprintf("peer_%d", i))) + peers = append(peers, p) + } + + var dhts []*IpfsDHT + for i := 0; i < 4; i++ { + d,err := NewDHT(peers[i]) + if err != nil { + t.Fatal(err) + } + dhts = append(dhts, d) + d.Start() + } + + _, err := dhts[0].Connect(addrs[1]) + if err != nil { + t.Fatal(err) + } + + _, err = dhts[1].Connect(addrs[2]) + if err != nil { + t.Fatal(err) + } + + _, err = dhts[1].Connect(addrs[3]) + if err != nil { + t.Fatal(err) + } + + err = dhts[3].PutLocal(u.Key("hello"), []byte("world")) + if err != nil { + t.Fatal(err) + } + + err = dhts[3].Provide(u.Key("hello")) + if err != nil { + t.Fatal(err) + } + + time.Sleep(time.Millisecond * 60) + + provs,err := dhts[0].FindProviders(u.Key("hello"), time.Second) + if err != nil { + t.Fatal(err) + } + + if len(provs) != 1 { + t.Fatal("Didnt get back providers") + } +} + + From ae6285e5a3253a9f8e989e2b11f67d755d3f4db0 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Fri, 8 Aug 2014 18:09:21 -0700 Subject: [PATCH 18/30] address issues from code review (issue #25) --- peer/peer.go | 23 +- routing/dht/{pDHTMessage.go => DHTMessage.go} | 16 +- routing/dht/bucket.go | 2 +- routing/dht/dht.go | 278 +++++++++--------- routing/dht/dht_test.go | 46 +-- routing/dht/diag.go | 2 +- routing/dht/messages.pb.go | 108 ++++--- routing/dht/messages.proto | 10 +- routing/dht/routing.go | 85 +++--- routing/dht/table.go | 2 +- routing/dht/util.go | 4 + swarm/swarm.go | 74 ++++- util/util.go | 16 +- 13 files changed, 383 insertions(+), 283 deletions(-) rename routing/dht/{pDHTMessage.go => DHTMessage.go} (56%) diff --git a/peer/peer.go b/peer/peer.go index 8b6ff4d90..f09c4c03d 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -1,13 +1,13 @@ package peer import ( - "encoding/hex" "time" "sync" u "github.com/jbenet/go-ipfs/util" ma "github.com/jbenet/go-multiaddr" mh "github.com/jbenet/go-multihash" + b58 "github.com/jbenet/go-base58" "bytes" ) @@ -21,7 +21,7 @@ func (id ID) Equal(other ID) bool { } func (id ID) Pretty() string { - return hex.EncodeToString(id) + return b58.Encode(id) } // Map maps Key (string) : *Peer (slices are not comparable). @@ -33,8 +33,8 @@ type Peer struct { ID ID Addresses []*ma.Multiaddr - distance time.Duration - distLock sync.RWMutex + latency time.Duration + latenLock sync.RWMutex } // Key returns the ID as a Key (string) for maps. @@ -64,12 +64,15 @@ func (p *Peer) NetAddress(n string) *ma.Multiaddr { return nil } -func (p *Peer) GetDistance() time.Duration { - return p.distance +func (p *Peer) GetLatency() (out time.Duration) { + p.latenLock.RLock() + out = p.latency + p.latenLock.RUnlock() + return } -func (p *Peer) SetDistance(dist time.Duration) { - p.distLock.Lock() - p.distance = dist - p.distLock.Unlock() +func (p *Peer) SetLatency(laten time.Duration) { + p.latenLock.Lock() + p.latency = laten + p.latenLock.Unlock() } diff --git a/routing/dht/pDHTMessage.go b/routing/dht/DHTMessage.go similarity index 56% rename from routing/dht/pDHTMessage.go rename to routing/dht/DHTMessage.go index bfe37d35f..64ca5bcab 100644 --- a/routing/dht/pDHTMessage.go +++ b/routing/dht/DHTMessage.go @@ -1,17 +1,17 @@ package dht // A helper struct to make working with protbuf types easier -type pDHTMessage struct { - Type DHTMessage_MessageType - Key string - Value []byte +type DHTMessage struct { + Type PBDHTMessage_MessageType + Key string + Value []byte Response bool - Id uint64 - Success bool + Id uint64 + Success bool } -func (m *pDHTMessage) ToProtobuf() *DHTMessage { - pmes := new(DHTMessage) +func (m *DHTMessage) ToProtobuf() *PBDHTMessage { + pmes := new(PBDHTMessage) if m.Value != nil { pmes.Value = m.Value } diff --git a/routing/dht/bucket.go b/routing/dht/bucket.go index 996d299d9..7aa8d0a94 100644 --- a/routing/dht/bucket.go +++ b/routing/dht/bucket.go @@ -49,7 +49,7 @@ func (b *Bucket) Split(cpl int, target ID) *Bucket { e := bucket_list.Front() for e != nil { peer_id := convertPeerID(e.Value.(*peer.Peer).ID) - peer_cpl := xor(peer_id, target).commonPrefixLen() + peer_cpl := prefLen(peer_id, target) if peer_cpl > cpl { cur := e out.PushBack(e.Value) diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 47f93e65f..8fbd5c092 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -1,15 +1,15 @@ package dht import ( - "sync" - "time" "bytes" "encoding/json" + "errors" + "sync" + "time" - peer "github.com/jbenet/go-ipfs/peer" - swarm "github.com/jbenet/go-ipfs/swarm" - u "github.com/jbenet/go-ipfs/util" - identify "github.com/jbenet/go-ipfs/identify" + peer "github.com/jbenet/go-ipfs/peer" + swarm "github.com/jbenet/go-ipfs/swarm" + u "github.com/jbenet/go-ipfs/util" ma "github.com/jbenet/go-multiaddr" @@ -37,7 +37,7 @@ type IpfsDHT struct { // Map keys to peers that can provide their value // TODO: implement a TTL on each of these keys - providers map[u.Key][]*providerInfo + providers map[u.Key][]*providerInfo providerLock sync.RWMutex // map of channels waiting for reply messages @@ -54,21 +54,27 @@ type IpfsDHT struct { diaglock sync.Mutex } +// The listen info struct holds information about a message that is being waited for type listenInfo struct { + // Responses matching the listen ID will be sent through resp resp chan *swarm.Message + + // count is the number of responses to listen for count int + + // eol is the time at which this listener will expire eol time.Time } // Create a new DHT object with the given peer as the 'local' host func NewDHT(p *peer.Peer) (*IpfsDHT, error) { if p == nil { - panic("Tried to create new dht with nil peer") + return nil, errors.New("nil peer passed to NewDHT()") } network := swarm.NewSwarm(p) err := network.Listen() if err != nil { - return nil,err + return nil, err } dht := new(IpfsDHT) @@ -90,50 +96,24 @@ func (dht *IpfsDHT) Start() { } // Connect to a new peer at the given address -// TODO: move this into swarm func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { - maddrstr,_ := addr.String() + maddrstr, _ := addr.String() u.DOut("Connect to new peer: %s", maddrstr) - if addr == nil { - panic("addr was nil!") - } - peer := new(peer.Peer) - peer.AddAddress(addr) - - conn,err := swarm.Dial("tcp", peer) + npeer, err := dht.network.Connect(addr) if err != nil { return nil, err } - err = identify.Handshake(dht.self, peer, conn.Incoming.MsgChan, conn.Outgoing.MsgChan) - if err != nil { - return nil, err - } - - // Send node an address that you can be reached on - myaddr := dht.self.NetAddress("tcp") - mastr,err := myaddr.String() - if err != nil { - panic("No local address to send") - } - - conn.Outgoing.MsgChan <- []byte(mastr) - - dht.network.StartConn(conn) - - removed := dht.routes[0].Update(peer) - if removed != nil { - panic("need to remove this peer.") - } + dht.Update(npeer) // Ping new peer to register in their routing table // NOTE: this should be done better... - err = dht.Ping(peer, time.Second * 2) + err = dht.Ping(npeer, time.Second*2) if err != nil { - panic("Failed to ping new peer.") + return nil, errors.New("Failed to ping newly connected peer.") } - return peer, nil + return npeer, nil } // Read in all messages from swarm and handle them appropriately @@ -144,23 +124,19 @@ func (dht *IpfsDHT) handleMessages() { checkTimeouts := time.NewTicker(time.Minute * 5) for { select { - case mes,ok := <-dht.network.Chan.Incoming: + case mes, ok := <-dht.network.Chan.Incoming: if !ok { u.DOut("handleMessages closing, bad recv on incoming") return } - pmes := new(DHTMessage) + pmes := new(PBDHTMessage) err := proto.Unmarshal(mes.Data, pmes) if err != nil { u.PErr("Failed to decode protobuf message: %s", err) continue } - // Update peers latest visit in routing table - removed := dht.routes[0].Update(mes.Peer) - if removed != nil { - panic("Need to handle removed peer.") - } + dht.Update(mes.Peer) // Note: not sure if this is the correct place for this if pmes.GetResponse() { @@ -180,7 +156,6 @@ func (dht *IpfsDHT) handleMessages() { dht.Unlisten(pmes.GetId()) } } else { - // this is expected behaviour during a timeout u.DOut("Received response with nobody listening...") } @@ -190,65 +165,73 @@ func (dht *IpfsDHT) handleMessages() { u.DOut("[peer: %s]", dht.self.ID.Pretty()) u.DOut("Got message type: '%s' [id = %x, from = %s]", - DHTMessage_MessageType_name[int32(pmes.GetType())], + PBDHTMessage_MessageType_name[int32(pmes.GetType())], pmes.GetId(), mes.Peer.ID.Pretty()) switch pmes.GetType() { - case DHTMessage_GET_VALUE: + case PBDHTMessage_GET_VALUE: dht.handleGetValue(mes.Peer, pmes) - case DHTMessage_PUT_VALUE: + case PBDHTMessage_PUT_VALUE: dht.handlePutValue(mes.Peer, pmes) - case DHTMessage_FIND_NODE: + case PBDHTMessage_FIND_NODE: dht.handleFindPeer(mes.Peer, pmes) - case DHTMessage_ADD_PROVIDER: + case PBDHTMessage_ADD_PROVIDER: dht.handleAddProvider(mes.Peer, pmes) - case DHTMessage_GET_PROVIDERS: + case PBDHTMessage_GET_PROVIDERS: dht.handleGetProviders(mes.Peer, pmes) - case DHTMessage_PING: + case PBDHTMessage_PING: dht.handlePing(mes.Peer, pmes) - case DHTMessage_DIAGNOSTIC: + case PBDHTMessage_DIAGNOSTIC: dht.handleDiagnostic(mes.Peer, pmes) } case err := <-dht.network.Chan.Errors: u.DErr("dht err: %s", err) - panic(err) case <-dht.shutdown: checkTimeouts.Stop() return case <-checkTimeouts.C: - dht.providerLock.Lock() - for k,parr := range dht.providers { - var cleaned []*providerInfo - for _,v := range parr { - if time.Since(v.Creation) < time.Hour { - cleaned = append(cleaned, v) - } - } - dht.providers[k] = cleaned - } - dht.providerLock.Unlock() - dht.listenLock.Lock() - var remove []uint64 - now := time.Now() - for k,v := range dht.listeners { - if now.After(v.eol) { - remove = append(remove, k) - } - } - for _,k := range remove { - delete(dht.listeners, k) - } - dht.listenLock.Unlock() + // Time to collect some garbage! + dht.cleanExpiredProviders() + dht.cleanExpiredListeners() } } } +func (dht *IpfsDHT) cleanExpiredProviders() { + dht.providerLock.Lock() + for k, parr := range dht.providers { + var cleaned []*providerInfo + for _, v := range parr { + if time.Since(v.Creation) < time.Hour { + cleaned = append(cleaned, v) + } + } + dht.providers[k] = cleaned + } + dht.providerLock.Unlock() +} + +func (dht *IpfsDHT) cleanExpiredListeners() { + dht.listenLock.Lock() + var remove []uint64 + now := time.Now() + for k, v := range dht.listeners { + if now.After(v.eol) { + remove = append(remove, k) + } + } + for _, k := range remove { + delete(dht.listeners, k) + } + dht.listenLock.Unlock() +} + func (dht *IpfsDHT) putValueToPeer(p *peer.Peer, key string, value []byte) error { - pmes := pDHTMessage{ - Type: DHTMessage_PUT_VALUE, - Key: key, + pmes := DHTMessage{ + Type: PBDHTMessage_PUT_VALUE, + Key: key, Value: value, - Id: GenerateMessageID(), + Id: GenerateMessageID(), } mes := swarm.NewMessage(p, pmes.ToProtobuf()) @@ -256,27 +239,27 @@ func (dht *IpfsDHT) putValueToPeer(p *peer.Peer, key string, value []byte) error return nil } -func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *DHTMessage) { +func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *PBDHTMessage) { dskey := ds.NewKey(pmes.GetKey()) - var resp *pDHTMessage + var resp *DHTMessage i_val, err := dht.datastore.Get(dskey) if err == nil { - resp = &pDHTMessage{ + resp = &DHTMessage{ Response: true, - Id: *pmes.Id, - Key: *pmes.Key, - Value: i_val.([]byte), - Success: true, + Id: *pmes.Id, + Key: *pmes.Key, + Value: i_val.([]byte), + Success: true, } } else if err == ds.ErrNotFound { // Find closest peer(s) to desired key and reply with that info closer := dht.routes[0].NearestPeer(convertKey(u.Key(pmes.GetKey()))) - resp = &pDHTMessage{ + resp = &DHTMessage{ Response: true, - Id: *pmes.Id, - Key: *pmes.Key, - Value: closer.ID, - Success: false, + Id: *pmes.Id, + Key: *pmes.Key, + Value: closer.ID, + Success: false, } } @@ -285,7 +268,7 @@ func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *DHTMessage) { } // Store a value in this peer local storage -func (dht *IpfsDHT) handlePutValue(p *peer.Peer, pmes *DHTMessage) { +func (dht *IpfsDHT) handlePutValue(p *peer.Peer, pmes *PBDHTMessage) { dskey := ds.NewKey(pmes.GetKey()) err := dht.datastore.Put(dskey, pmes.GetValue()) if err != nil { @@ -294,46 +277,51 @@ func (dht *IpfsDHT) handlePutValue(p *peer.Peer, pmes *DHTMessage) { } } -func (dht *IpfsDHT) handlePing(p *peer.Peer, pmes *DHTMessage) { - resp := pDHTMessage{ - Type: pmes.GetType(), +func (dht *IpfsDHT) handlePing(p *peer.Peer, pmes *PBDHTMessage) { + resp := DHTMessage{ + Type: pmes.GetType(), Response: true, - Id: pmes.GetId(), + Id: pmes.GetId(), } - dht.network.Chan.Outgoing <-swarm.NewMessage(p, resp.ToProtobuf()) + dht.network.Chan.Outgoing <- swarm.NewMessage(p, resp.ToProtobuf()) } -func (dht *IpfsDHT) handleFindPeer(p *peer.Peer, pmes *DHTMessage) { +func (dht *IpfsDHT) handleFindPeer(p *peer.Peer, pmes *PBDHTMessage) { + success := true u.POut("handleFindPeer: searching for '%s'", peer.ID(pmes.GetKey()).Pretty()) closest := dht.routes[0].NearestPeer(convertKey(u.Key(pmes.GetKey()))) if closest == nil { - panic("could not find anything.") + u.PErr("handleFindPeer: could not find anything.") + success = false } if len(closest.Addresses) == 0 { - panic("no addresses for connected peer...") + u.PErr("handleFindPeer: no addresses for connected peer...") + success = false } u.POut("handleFindPeer: sending back '%s'", closest.ID.Pretty()) - addr,err := closest.Addresses[0].String() + addr, err := closest.Addresses[0].String() if err != nil { - panic(err) + u.PErr(err.Error()) + success = false } - resp := pDHTMessage{ - Type: pmes.GetType(), + resp := DHTMessage{ + Type: pmes.GetType(), Response: true, - Id: pmes.GetId(), - Value: []byte(addr), + Id: pmes.GetId(), + Value: []byte(addr), + Success: success, } mes := swarm.NewMessage(p, resp.ToProtobuf()) - dht.network.Chan.Outgoing <-mes + dht.network.Chan.Outgoing <- mes } -func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *DHTMessage) { +func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *PBDHTMessage) { dht.providerLock.RLock() providers := dht.providers[u.Key(pmes.GetKey())] dht.providerLock.RUnlock() @@ -344,9 +332,9 @@ func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *DHTMessage) { // This is just a quick hack, formalize method of sending addrs later addrs := make(map[u.Key]string) - for _,prov := range providers { + for _, prov := range providers { ma := prov.Value.NetAddress("tcp") - str,err := ma.String() + str, err := ma.String() if err != nil { u.PErr("Error: %s", err) continue @@ -355,35 +343,38 @@ func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *DHTMessage) { addrs[prov.Value.Key()] = str } - data,err := json.Marshal(addrs) + success := true + data, err := json.Marshal(addrs) if err != nil { - panic(err) + u.POut("handleGetProviders: error marshalling struct to JSON: %s", err) + data = nil + success = false } - resp := pDHTMessage{ - Type: DHTMessage_GET_PROVIDERS, - Key: pmes.GetKey(), - Value: data, - Id: pmes.GetId(), + resp := DHTMessage{ + Type: PBDHTMessage_GET_PROVIDERS, + Key: pmes.GetKey(), + Value: data, + Id: pmes.GetId(), Response: true, + Success: success, } mes := swarm.NewMessage(p, resp.ToProtobuf()) - dht.network.Chan.Outgoing <-mes + dht.network.Chan.Outgoing <- mes } type providerInfo struct { Creation time.Time - Value *peer.Peer + Value *peer.Peer } -func (dht *IpfsDHT) handleAddProvider(p *peer.Peer, pmes *DHTMessage) { +func (dht *IpfsDHT) handleAddProvider(p *peer.Peer, pmes *PBDHTMessage) { //TODO: need to implement TTLs on providers key := u.Key(pmes.GetKey()) dht.addProviderEntry(key, p) } - // Register a handler for a specific message ID, used for getting replies // to certain messages (i.e. response to a GET_VALUE message) func (dht *IpfsDHT) ListenFor(mesid uint64, count int, timeout time.Duration) <-chan *swarm.Message { @@ -407,7 +398,7 @@ func (dht *IpfsDHT) Unlisten(mesid uint64) { func (dht *IpfsDHT) IsListening(mesid uint64) bool { dht.listenLock.RLock() - li,ok := dht.listeners[mesid] + li, ok := dht.listeners[mesid] dht.listenLock.RUnlock() if time.Now().After(li.eol) { dht.listenLock.Lock() @@ -432,7 +423,7 @@ func (dht *IpfsDHT) addProviderEntry(key u.Key, p *peer.Peer) { dht.providerLock.Unlock() } -func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *DHTMessage) { +func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *PBDHTMessage) { dht.diaglock.Lock() if dht.IsListening(pmes.GetId()) { //TODO: ehhh.......... @@ -442,15 +433,13 @@ func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *DHTMessage) { dht.diaglock.Unlock() seq := dht.routes[0].NearestPeers(convertPeerID(dht.self.ID), 10) - listen_chan := dht.ListenFor(pmes.GetId(), len(seq), time.Second * 30) + listen_chan := dht.ListenFor(pmes.GetId(), len(seq), time.Second*30) - for _,ps := range seq { + for _, ps := range seq { mes := swarm.NewMessage(ps, pmes) - dht.network.Chan.Outgoing <-mes + dht.network.Chan.Outgoing <- mes } - - buf := new(bytes.Buffer) di := dht.getDiagInfo() buf.Write(di.Marshal()) @@ -464,7 +453,7 @@ func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *DHTMessage) { //Timeout, return what we have goto out case req_resp := <-listen_chan: - pmes_out := new(DHTMessage) + pmes_out := new(PBDHTMessage) err := proto.Unmarshal(req_resp.Data, pmes_out) if err != nil { // It broke? eh, whatever, keep going @@ -476,19 +465,19 @@ func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *DHTMessage) { } out: - resp := pDHTMessage{ - Type: DHTMessage_DIAGNOSTIC, - Id: pmes.GetId(), - Value: buf.Bytes(), + resp := DHTMessage{ + Type: PBDHTMessage_DIAGNOSTIC, + Id: pmes.GetId(), + Value: buf.Bytes(), Response: true, } mes := swarm.NewMessage(p, resp.ToProtobuf()) - dht.network.Chan.Outgoing <-mes + dht.network.Chan.Outgoing <- mes } func (dht *IpfsDHT) GetLocal(key u.Key) ([]byte, error) { - v,err := dht.datastore.Get(ds.NewKey(string(key))) + v, err := dht.datastore.Get(ds.NewKey(string(key))) if err != nil { return nil, err } @@ -498,3 +487,10 @@ func (dht *IpfsDHT) GetLocal(key u.Key) ([]byte, error) { func (dht *IpfsDHT) PutLocal(key u.Key, value []byte) error { return dht.datastore.Put(ds.NewKey(string(key)), value) } + +func (dht *IpfsDHT) Update(p *peer.Peer) { + removed := dht.routes[0].Update(p) + if removed != nil { + dht.network.Drop(removed) + } +} diff --git a/routing/dht/dht_test.go b/routing/dht/dht_test.go index 2de027a3d..e24ada020 100644 --- a/routing/dht/dht_test.go +++ b/routing/dht/dht_test.go @@ -2,21 +2,22 @@ package dht import ( "testing" - peer "github.com/jbenet/go-ipfs/peer" - ma "github.com/jbenet/go-multiaddr" - u "github.com/jbenet/go-ipfs/util" - "time" + peer "github.com/jbenet/go-ipfs/peer" + u "github.com/jbenet/go-ipfs/util" + ma "github.com/jbenet/go-multiaddr" + "fmt" + "time" ) func TestPing(t *testing.T) { u.Debug = false - addr_a,err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/1234") + addr_a, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/1234") if err != nil { t.Fatal(err) } - addr_b,err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5678") + addr_b, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5678") if err != nil { t.Fatal(err) } @@ -29,12 +30,12 @@ func TestPing(t *testing.T) { peer_b.AddAddress(addr_b) peer_b.ID = peer.ID([]byte("peer_b")) - dht_a,err := NewDHT(peer_a) + dht_a, err := NewDHT(peer_a) if err != nil { t.Fatal(err) } - dht_b,err := NewDHT(peer_b) + dht_b, err := NewDHT(peer_b) if err != nil { t.Fatal(err) } @@ -42,13 +43,13 @@ func TestPing(t *testing.T) { dht_a.Start() dht_b.Start() - _,err = dht_a.Connect(addr_b) + _, err = dht_a.Connect(addr_b) if err != nil { t.Fatal(err) } //Test that we can ping the node - err = dht_a.Ping(peer_b, time.Second * 2) + err = dht_a.Ping(peer_b, time.Second*2) if err != nil { t.Fatal(err) } @@ -59,11 +60,11 @@ func TestPing(t *testing.T) { func TestValueGetSet(t *testing.T) { u.Debug = false - addr_a,err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/1235") + addr_a, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/1235") if err != nil { t.Fatal(err) } - addr_b,err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5679") + addr_b, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5679") if err != nil { t.Fatal(err) } @@ -76,12 +77,12 @@ func TestValueGetSet(t *testing.T) { peer_b.AddAddress(addr_b) peer_b.ID = peer.ID([]byte("peer_b")) - dht_a,err := NewDHT(peer_a) + dht_a, err := NewDHT(peer_a) if err != nil { t.Fatal(err) } - dht_b,err := NewDHT(peer_b) + dht_b, err := NewDHT(peer_b) if err != nil { t.Fatal(err) } @@ -89,7 +90,7 @@ func TestValueGetSet(t *testing.T) { dht_a.Start() dht_b.Start() - _,err = dht_a.Connect(addr_b) + _, err = dht_a.Connect(addr_b) if err != nil { t.Fatal(err) } @@ -99,7 +100,7 @@ func TestValueGetSet(t *testing.T) { t.Fatal(err) } - val, err := dht_a.GetValue("hello", time.Second * 2) + val, err := dht_a.GetValue("hello", time.Second*2) if err != nil { t.Fatal(err) } @@ -113,14 +114,13 @@ func TestProvides(t *testing.T) { u.Debug = false var addrs []*ma.Multiaddr for i := 0; i < 4; i++ { - a,err := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 5000 + i)) + a, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 5000+i)) if err != nil { t.Fatal(err) } addrs = append(addrs, a) } - var peers []*peer.Peer for i := 0; i < 4; i++ { p := new(peer.Peer) @@ -131,7 +131,7 @@ func TestProvides(t *testing.T) { var dhts []*IpfsDHT for i := 0; i < 4; i++ { - d,err := NewDHT(peers[i]) + d, err := NewDHT(peers[i]) if err != nil { t.Fatal(err) } @@ -166,7 +166,7 @@ func TestProvides(t *testing.T) { time.Sleep(time.Millisecond * 60) - provs,err := dhts[0].FindProviders(u.Key("hello"), time.Second) + provs, err := dhts[0].FindProviders(u.Key("hello"), time.Second) if err != nil { t.Fatal(err) } @@ -174,6 +174,8 @@ func TestProvides(t *testing.T) { if len(provs) != 1 { t.Fatal("Didnt get back providers") } + + for i := 0; i < 4; i++ { + dhts[i].Halt() + } } - - diff --git a/routing/dht/diag.go b/routing/dht/diag.go index b8f211e40..4bc752f92 100644 --- a/routing/dht/diag.go +++ b/routing/dht/diag.go @@ -38,7 +38,7 @@ func (dht *IpfsDHT) getDiagInfo() *diagInfo { di.Keys = nil // Currently no way to query datastore for _,p := range dht.routes[0].listpeers() { - di.Connections = append(di.Connections, connDiagInfo{p.GetDistance(), p.ID}) + di.Connections = append(di.Connections, connDiagInfo{p.GetLatency(), p.ID}) } return di } diff --git a/routing/dht/messages.pb.go b/routing/dht/messages.pb.go index 4f427efa1..a852c5e1f 100644 --- a/routing/dht/messages.pb.go +++ b/routing/dht/messages.pb.go @@ -9,7 +9,7 @@ It is generated from these files: messages.proto It has these top-level messages: - DHTMessage + PBDHTMessage */ package dht @@ -20,19 +20,19 @@ import math "math" var _ = proto.Marshal var _ = math.Inf -type DHTMessage_MessageType int32 +type PBDHTMessage_MessageType int32 const ( - DHTMessage_PUT_VALUE DHTMessage_MessageType = 0 - DHTMessage_GET_VALUE DHTMessage_MessageType = 1 - DHTMessage_ADD_PROVIDER DHTMessage_MessageType = 2 - DHTMessage_GET_PROVIDERS DHTMessage_MessageType = 3 - DHTMessage_FIND_NODE DHTMessage_MessageType = 4 - DHTMessage_PING DHTMessage_MessageType = 5 - DHTMessage_DIAGNOSTIC DHTMessage_MessageType = 6 + PBDHTMessage_PUT_VALUE PBDHTMessage_MessageType = 0 + PBDHTMessage_GET_VALUE PBDHTMessage_MessageType = 1 + PBDHTMessage_ADD_PROVIDER PBDHTMessage_MessageType = 2 + PBDHTMessage_GET_PROVIDERS PBDHTMessage_MessageType = 3 + PBDHTMessage_FIND_NODE PBDHTMessage_MessageType = 4 + PBDHTMessage_PING PBDHTMessage_MessageType = 5 + PBDHTMessage_DIAGNOSTIC PBDHTMessage_MessageType = 6 ) -var DHTMessage_MessageType_name = map[int32]string{ +var PBDHTMessage_MessageType_name = map[int32]string{ 0: "PUT_VALUE", 1: "GET_VALUE", 2: "ADD_PROVIDER", @@ -41,7 +41,7 @@ var DHTMessage_MessageType_name = map[int32]string{ 5: "PING", 6: "DIAGNOSTIC", } -var DHTMessage_MessageType_value = map[string]int32{ +var PBDHTMessage_MessageType_value = map[string]int32{ "PUT_VALUE": 0, "GET_VALUE": 1, "ADD_PROVIDER": 2, @@ -51,79 +51,111 @@ var DHTMessage_MessageType_value = map[string]int32{ "DIAGNOSTIC": 6, } -func (x DHTMessage_MessageType) Enum() *DHTMessage_MessageType { - p := new(DHTMessage_MessageType) +func (x PBDHTMessage_MessageType) Enum() *PBDHTMessage_MessageType { + p := new(PBDHTMessage_MessageType) *p = x return p } -func (x DHTMessage_MessageType) String() string { - return proto.EnumName(DHTMessage_MessageType_name, int32(x)) +func (x PBDHTMessage_MessageType) String() string { + return proto.EnumName(PBDHTMessage_MessageType_name, int32(x)) } -func (x *DHTMessage_MessageType) UnmarshalJSON(data []byte) error { - value, err := proto.UnmarshalJSONEnum(DHTMessage_MessageType_value, data, "DHTMessage_MessageType") +func (x *PBDHTMessage_MessageType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(PBDHTMessage_MessageType_value, data, "PBDHTMessage_MessageType") if err != nil { return err } - *x = DHTMessage_MessageType(value) + *x = PBDHTMessage_MessageType(value) return nil } -type DHTMessage struct { - Type *DHTMessage_MessageType `protobuf:"varint,1,req,name=type,enum=dht.DHTMessage_MessageType" json:"type,omitempty"` - Key *string `protobuf:"bytes,2,opt,name=key" json:"key,omitempty"` - Value []byte `protobuf:"bytes,3,opt,name=value" json:"value,omitempty"` - Id *uint64 `protobuf:"varint,4,req,name=id" json:"id,omitempty"` - Response *bool `protobuf:"varint,5,opt,name=response" json:"response,omitempty"` - Success *bool `protobuf:"varint,6,opt,name=success" json:"success,omitempty"` - XXX_unrecognized []byte `json:"-"` +type PBDHTMessage struct { + Type *PBDHTMessage_MessageType `protobuf:"varint,1,req,name=type,enum=dht.PBDHTMessage_MessageType" json:"type,omitempty"` + Key *string `protobuf:"bytes,2,opt,name=key" json:"key,omitempty"` + Value []byte `protobuf:"bytes,3,opt,name=value" json:"value,omitempty"` + Id *uint64 `protobuf:"varint,4,req,name=id" json:"id,omitempty"` + Response *bool `protobuf:"varint,5,opt,name=response" json:"response,omitempty"` + Success *bool `protobuf:"varint,6,opt,name=success" json:"success,omitempty"` + Peers []*PBDHTMessage_PBPeer `protobuf:"bytes,7,rep,name=peers" json:"peers,omitempty"` + XXX_unrecognized []byte `json:"-"` } -func (m *DHTMessage) Reset() { *m = DHTMessage{} } -func (m *DHTMessage) String() string { return proto.CompactTextString(m) } -func (*DHTMessage) ProtoMessage() {} +func (m *PBDHTMessage) Reset() { *m = PBDHTMessage{} } +func (m *PBDHTMessage) String() string { return proto.CompactTextString(m) } +func (*PBDHTMessage) ProtoMessage() {} -func (m *DHTMessage) GetType() DHTMessage_MessageType { +func (m *PBDHTMessage) GetType() PBDHTMessage_MessageType { if m != nil && m.Type != nil { return *m.Type } - return DHTMessage_PUT_VALUE + return PBDHTMessage_PUT_VALUE } -func (m *DHTMessage) GetKey() string { +func (m *PBDHTMessage) GetKey() string { if m != nil && m.Key != nil { return *m.Key } return "" } -func (m *DHTMessage) GetValue() []byte { +func (m *PBDHTMessage) GetValue() []byte { if m != nil { return m.Value } return nil } -func (m *DHTMessage) GetId() uint64 { +func (m *PBDHTMessage) GetId() uint64 { if m != nil && m.Id != nil { return *m.Id } return 0 } -func (m *DHTMessage) GetResponse() bool { +func (m *PBDHTMessage) GetResponse() bool { if m != nil && m.Response != nil { return *m.Response } return false } -func (m *DHTMessage) GetSuccess() bool { +func (m *PBDHTMessage) GetSuccess() bool { if m != nil && m.Success != nil { return *m.Success } return false } -func init() { - proto.RegisterEnum("dht.DHTMessage_MessageType", DHTMessage_MessageType_name, DHTMessage_MessageType_value) +func (m *PBDHTMessage) GetPeers() []*PBDHTMessage_PBPeer { + if m != nil { + return m.Peers + } + return nil +} + +type PBDHTMessage_PBPeer struct { + Id *string `protobuf:"bytes,1,req,name=id" json:"id,omitempty"` + Addr *string `protobuf:"bytes,2,req,name=addr" json:"addr,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *PBDHTMessage_PBPeer) Reset() { *m = PBDHTMessage_PBPeer{} } +func (m *PBDHTMessage_PBPeer) String() string { return proto.CompactTextString(m) } +func (*PBDHTMessage_PBPeer) ProtoMessage() {} + +func (m *PBDHTMessage_PBPeer) GetId() string { + if m != nil && m.Id != nil { + return *m.Id + } + return "" +} + +func (m *PBDHTMessage_PBPeer) GetAddr() string { + if m != nil && m.Addr != nil { + return *m.Addr + } + return "" +} + +func init() { + proto.RegisterEnum("dht.PBDHTMessage_MessageType", PBDHTMessage_MessageType_name, PBDHTMessage_MessageType_value) } diff --git a/routing/dht/messages.proto b/routing/dht/messages.proto index 278a95202..4d4e8c61f 100644 --- a/routing/dht/messages.proto +++ b/routing/dht/messages.proto @@ -2,7 +2,7 @@ package dht; //run `protoc --go_out=. *.proto` to generate -message DHTMessage { +message PBDHTMessage { enum MessageType { PUT_VALUE = 0; GET_VALUE = 1; @@ -13,6 +13,11 @@ message DHTMessage { DIAGNOSTIC = 6; } + message PBPeer { + required string id = 1; + required string addr = 2; + } + required MessageType type = 1; optional string key = 2; optional bytes value = 3; @@ -23,4 +28,7 @@ message DHTMessage { // Signals whether or not this message is a response to another message optional bool response = 5; optional bool success = 6; + + // Used for returning peers from queries (normally, peers closer to X) + repeated PBPeer peers = 7; } diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 1a90ce76b..57d087fdc 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -1,10 +1,11 @@ package dht import ( - "math/rand" - "time" "bytes" "encoding/json" + "errors" + "math/rand" + "time" proto "code.google.com/p/goprotobuf/proto" @@ -34,7 +35,7 @@ func (s *IpfsDHT) PutValue(key u.Key, value []byte) error { var p *peer.Peer p = s.routes[0].NearestPeer(convertKey(key)) if p == nil { - panic("Table returned nil peer!") + return errors.New("Table returned nil peer!") } return s.putValueToPeer(p, string(key), value) @@ -47,13 +48,13 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { var p *peer.Peer p = s.routes[0].NearestPeer(convertKey(key)) if p == nil { - panic("Table returned nil peer!") + return nil, errors.New("Table returned nil peer!") } - pmes := pDHTMessage{ - Type: DHTMessage_GET_VALUE, - Key: string(key), - Id: GenerateMessageID(), + pmes := DHTMessage{ + Type: PBDHTMessage_GET_VALUE, + Key: string(key), + Id: GenerateMessageID(), } response_chan := s.ListenFor(pmes.Id, 1, time.Minute) @@ -68,15 +69,13 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { return nil, u.ErrTimeout case resp, ok := <-response_chan: if !ok { - panic("Channel was closed...") + u.PErr("response channel closed before timeout, please investigate.") + return nil, u.ErrTimeout } - if resp == nil { - panic("Why the hell is this response nil?") - } - pmes_out := new(DHTMessage) + pmes_out := new(PBDHTMessage) err := proto.Unmarshal(resp.Data, pmes_out) if err != nil { - return nil,err + return nil, err } if pmes_out.GetSuccess() { return pmes_out.GetValue(), nil @@ -96,15 +95,15 @@ func (s *IpfsDHT) Provide(key u.Key) error { //return an error } - pmes := pDHTMessage{ - Type: DHTMessage_ADD_PROVIDER, - Key: string(key), + pmes := DHTMessage{ + Type: PBDHTMessage_ADD_PROVIDER, + Key: string(key), } pbmes := pmes.ToProtobuf() - for _,p := range peers { + for _, p := range peers { mes := swarm.NewMessage(p, pbmes) - s.network.Chan.Outgoing <-mes + s.network.Chan.Outgoing <- mes } return nil } @@ -113,17 +112,17 @@ func (s *IpfsDHT) Provide(key u.Key) error { func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, error) { p := s.routes[0].NearestPeer(convertKey(key)) - pmes := pDHTMessage{ - Type: DHTMessage_GET_PROVIDERS, - Key: string(key), - Id: GenerateMessageID(), + pmes := DHTMessage{ + Type: PBDHTMessage_GET_PROVIDERS, + Key: string(key), + Id: GenerateMessageID(), } mes := swarm.NewMessage(p, pmes.ToProtobuf()) listen_chan := s.ListenFor(pmes.Id, 1, time.Minute) u.DOut("Find providers for: '%s'", key) - s.network.Chan.Outgoing <-mes + s.network.Chan.Outgoing <- mes after := time.After(timeout) select { case <-after: @@ -131,7 +130,7 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, return nil, u.ErrTimeout case resp := <-listen_chan: u.DOut("FindProviders: got response.") - pmes_out := new(DHTMessage) + pmes_out := new(PBDHTMessage) err := proto.Unmarshal(resp.Data, pmes_out) if err != nil { return nil, err @@ -143,10 +142,10 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, } var prov_arr []*peer.Peer - for pid,addr := range addrs { + for pid, addr := range addrs { p := s.network.Find(pid) if p == nil { - maddr,err := ma.NewMultiaddr(addr) + maddr, err := ma.NewMultiaddr(addr) if err != nil { u.PErr("error connecting to new peer: %s", err) continue @@ -171,23 +170,23 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error) { p := s.routes[0].NearestPeer(convertPeerID(id)) - pmes := pDHTMessage{ - Type: DHTMessage_FIND_NODE, - Key: string(id), - Id: GenerateMessageID(), + pmes := DHTMessage{ + Type: PBDHTMessage_FIND_NODE, + Key: string(id), + Id: GenerateMessageID(), } mes := swarm.NewMessage(p, pmes.ToProtobuf()) listen_chan := s.ListenFor(pmes.Id, 1, time.Minute) - s.network.Chan.Outgoing <-mes + s.network.Chan.Outgoing <- mes after := time.After(timeout) select { case <-after: s.Unlisten(pmes.Id) return nil, u.ErrTimeout case resp := <-listen_chan: - pmes_out := new(DHTMessage) + pmes_out := new(PBDHTMessage) err := proto.Unmarshal(resp.Data, pmes_out) if err != nil { return nil, err @@ -218,7 +217,7 @@ func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { // Thoughts: maybe this should accept an ID and do a peer lookup? u.DOut("Enter Ping.") - pmes := pDHTMessage{Id: GenerateMessageID(), Type: DHTMessage_PING} + pmes := DHTMessage{Id: GenerateMessageID(), Type: PBDHTMessage_PING} mes := swarm.NewMessage(p, pmes.ToProtobuf()) before := time.Now() @@ -229,7 +228,7 @@ func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { select { case <-response_chan: roundtrip := time.Since(before) - p.SetDistance(roundtrip) + p.SetLatency(roundtrip) u.POut("Ping took %s.", roundtrip.String()) return nil case <-tout: @@ -246,17 +245,17 @@ func (dht *IpfsDHT) GetDiagnostic(timeout time.Duration) ([]*diagInfo, error) { targets := dht.routes[0].NearestPeers(convertPeerID(dht.self.ID), 10) // TODO: Add timeout to this struct so nodes know when to return - pmes := pDHTMessage{ - Type: DHTMessage_DIAGNOSTIC, - Id: GenerateMessageID(), + pmes := DHTMessage{ + Type: PBDHTMessage_DIAGNOSTIC, + Id: GenerateMessageID(), } - listen_chan := dht.ListenFor(pmes.Id, len(targets), time.Minute * 2) + listen_chan := dht.ListenFor(pmes.Id, len(targets), time.Minute*2) pbmes := pmes.ToProtobuf() - for _,p := range targets { + for _, p := range targets { mes := swarm.NewMessage(p, pbmes) - dht.network.Chan.Outgoing <-mes + dht.network.Chan.Outgoing <- mes } var out []*diagInfo @@ -267,7 +266,7 @@ func (dht *IpfsDHT) GetDiagnostic(timeout time.Duration) ([]*diagInfo, error) { u.DOut("Diagnostic request timed out.") return out, u.ErrTimeout case resp := <-listen_chan: - pmes_out := new(DHTMessage) + pmes_out := new(PBDHTMessage) err := proto.Unmarshal(resp.Data, pmes_out) if err != nil { // NOTE: here and elsewhere, need to audit error handling, @@ -288,5 +287,5 @@ func (dht *IpfsDHT) GetDiagnostic(timeout time.Duration) ([]*diagInfo, error) { } } - return nil,nil + return nil, nil } diff --git a/routing/dht/table.go b/routing/dht/table.go index be4a4b392..7a121234f 100644 --- a/routing/dht/table.go +++ b/routing/dht/table.go @@ -125,7 +125,7 @@ func (rt *RoutingTable) NearestPeer(id ID) *peer.Peer { func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { rt.tabLock.RLock() defer rt.tabLock.RUnlock() - cpl := xor(id, rt.local).commonPrefixLen() + cpl := prefLen(id, rt.local) // Get bucket at cpl index or last bucket var bucket *Bucket diff --git a/routing/dht/util.go b/routing/dht/util.go index 2adc8b765..5961cd226 100644 --- a/routing/dht/util.go +++ b/routing/dht/util.go @@ -40,6 +40,10 @@ func (id ID) commonPrefixLen() int { return len(id)*8 - 1 } +func prefLen(a, b ID) int { + return xor(a, b).commonPrefixLen() +} + func xor(a, b ID) ID { a, b = equalizeSizes(a, b) diff --git a/swarm/swarm.go b/swarm/swarm.go index b286c3c82..93ad68755 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -1,15 +1,16 @@ package swarm import ( + "errors" "fmt" "net" "sync" + proto "code.google.com/p/goprotobuf/proto" + ident "github.com/jbenet/go-ipfs/identify" peer "github.com/jbenet/go-ipfs/peer" u "github.com/jbenet/go-ipfs/util" ma "github.com/jbenet/go-multiaddr" - ident "github.com/jbenet/go-ipfs/identify" - proto "code.google.com/p/goprotobuf/proto" ) // Message represents a packet of information sent to or received from a @@ -24,9 +25,10 @@ type Message struct { // Cleaner looking helper function to make a new message struct func NewMessage(p *peer.Peer, data proto.Message) *Message { - bytes,err := proto.Marshal(data) + bytes, err := proto.Marshal(data) if err != nil { - panic(err) + u.PErr(err.Error()) + return nil } return &Message{ Peer: p, @@ -63,7 +65,7 @@ func (se *SwarmListenErr) Error() string { return "" } var out string - for i,v := range se.Errors { + for i, v := range se.Errors { if v != nil { out += fmt.Sprintf("%d: %s\n", i, v) } @@ -80,7 +82,7 @@ type Swarm struct { conns ConnMap connsLock sync.RWMutex - local *peer.Peer + local *peer.Peer listeners []net.Listener } @@ -137,7 +139,7 @@ func (s *Swarm) connListen(maddr *ma.Multiaddr) error { if err != nil { e := fmt.Errorf("Failed to accept connection: %s - %s [%s]", netstr, addr, err) - go func() {s.Chan.Errors <- e}() + go func() { s.Chan.Errors <- e }() return } go s.handleNewConn(nconn) @@ -160,7 +162,9 @@ func (s *Swarm) handleNewConn(nconn net.Conn) { err := ident.Handshake(s.local, p, conn.Incoming.MsgChan, conn.Outgoing.MsgChan) if err != nil { - panic(err) + u.PErr(err.Error()) + conn.Close() + return } // Get address to contact remote peer from @@ -186,7 +190,7 @@ func (s *Swarm) Close() { s.Chan.Close <- true // fan out s.Chan.Close <- true // listener - for _,list := range s.listeners { + for _, list := range s.listeners { list.Close() } } @@ -220,9 +224,9 @@ func (s *Swarm) Dial(peer *peer.Peer) (*Conn, error) { return conn, nil } -func (s *Swarm) StartConn(conn *Conn) { +func (s *Swarm) StartConn(conn *Conn) error { if conn == nil { - panic("tried to start nil Conn!") + return errors.New("Tried to start nil connection.") } u.DOut("Starting connection: %s", conn.Peer.Key().Pretty()) @@ -233,6 +237,7 @@ func (s *Swarm) StartConn(conn *Conn) { // kick off reader goroutine go s.fanIn(conn) + return nil } // Handles the unwrapping + sending of messages to the right connection. @@ -303,3 +308,50 @@ func (s *Swarm) Find(key u.Key) *peer.Peer { } return conn.Peer } + +func (s *Swarm) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { + if addr == nil { + return nil, errors.New("nil Multiaddr passed to swarm.Connect()") + } + npeer := new(peer.Peer) + npeer.AddAddress(addr) + + conn, err := Dial("tcp", npeer) + if err != nil { + return nil, err + } + + err = ident.Handshake(s.local, npeer, conn.Incoming.MsgChan, conn.Outgoing.MsgChan) + if err != nil { + return nil, err + } + + // Send node an address that you can be reached on + myaddr := s.local.NetAddress("tcp") + mastr, err := myaddr.String() + if err != nil { + return nil, errors.New("No local address to send to peer.") + } + + conn.Outgoing.MsgChan <- []byte(mastr) + + s.StartConn(conn) + + return npeer, nil +} + +// Removes a given peer from the swarm and closes connections to it +func (s *Swarm) Drop(p *peer.Peer) error { + s.connsLock.RLock() + conn, found := s.conns[u.Key(p.ID)] + s.connsLock.RUnlock() + if !found { + return u.ErrNotFound + } + + s.connsLock.Lock() + delete(s.conns, u.Key(p.ID)) + s.connsLock.Unlock() + + return conn.Close() +} diff --git a/util/util.go b/util/util.go index ca9ab79d3..ac9ca8100 100644 --- a/util/util.go +++ b/util/util.go @@ -1,13 +1,14 @@ package util import ( - "fmt" "errors" - mh "github.com/jbenet/go-multihash" + "fmt" "os" "os/user" "strings" - "encoding/hex" + + b58 "github.com/jbenet/go-base58" + mh "github.com/jbenet/go-multihash" ) // Debug is a global flag for debugging. @@ -23,11 +24,14 @@ var ErrTimeout = errors.New("Error: Call timed out.") // find the expected node, but did find 'a' node. var ErrSearchIncomplete = errors.New("Error: Search Incomplete.") +// ErrNotFound is returned when a search fails to find anything +var ErrNotFound = errors.New("Error: Not Found.") + // Key is a string representation of multihash for use with maps. type Key string func (k Key) Pretty() string { - return hex.EncodeToString([]byte(k)) + return b58.Encode([]byte(k)) } // Hash is the global IPFS hash function. uses multihash SHA2_256, 256 bits @@ -51,12 +55,12 @@ func TildeExpansion(filename string) (string, error) { // PErr is a shorthand printing function to output to Stderr. func PErr(format string, a ...interface{}) { - fmt.Fprintf(os.Stderr, format + "\n", a...) + fmt.Fprintf(os.Stderr, format+"\n", a...) } // POut is a shorthand printing function to output to Stdout. func POut(format string, a ...interface{}) { - fmt.Fprintf(os.Stdout, format + "\n", a...) + fmt.Fprintf(os.Stdout, format+"\n", a...) } // DErr is a shorthand debug printing function to output to Stderr. From 1eaeb3ba290af1dda9688c3f2716214c8be56da0 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Fri, 8 Aug 2014 19:49:27 -0700 Subject: [PATCH 19/30] make tests pass a little more reliably by changing a port to not overlap --- routing/dht/dht_test.go | 2 +- swarm/swarm_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/routing/dht/dht_test.go b/routing/dht/dht_test.go index e24ada020..a5b47cb21 100644 --- a/routing/dht/dht_test.go +++ b/routing/dht/dht_test.go @@ -13,7 +13,7 @@ import ( func TestPing(t *testing.T) { u.Debug = false - addr_a, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/1234") + addr_a, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/2222") if err != nil { t.Fatal(err) } diff --git a/swarm/swarm_test.go b/swarm/swarm_test.go index e289a5e77..4acfe1b71 100644 --- a/swarm/swarm_test.go +++ b/swarm/swarm_test.go @@ -43,8 +43,8 @@ func pong(c net.Conn, peer *peer.Peer) { func TestSwarm(t *testing.T) { swarm := NewSwarm(nil) - peers := []*peer.Peer{} - listeners := []*net.Listener{} + var peers []*peer.Peer + var listeners []net.Listener peerNames := map[string]string{ "11140beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a30": "/ip4/127.0.0.1/tcp/1234", "11140beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a31": "/ip4/127.0.0.1/tcp/2345", @@ -78,7 +78,7 @@ func TestSwarm(t *testing.T) { // ok done, add it. peers = append(peers, peer) - listeners = append(listeners, &listener) + listeners = append(listeners, listener) } MsgNum := 1000 @@ -112,6 +112,6 @@ func TestSwarm(t *testing.T) { fmt.Println("closing") swarm.Close() for _, listener := range listeners { - (*listener).(*net.TCPListener).Close() + listener.(*net.TCPListener).Close() } } From 9f7604378c7fa1d3535186f7a70d62ace5dc6d50 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Fri, 8 Aug 2014 19:58:42 -0700 Subject: [PATCH 20/30] moved routing table code into its own package --- routing/dht/dht.go | 13 +++++++------ routing/dht/diag.go | 2 +- routing/dht/routing.go | 13 +++++++------ routing/{dht => kbucket}/bucket.go | 2 +- routing/{dht => kbucket}/table.go | 6 +++--- routing/{dht => kbucket}/table_test.go | 18 +++++++++--------- routing/{dht => kbucket}/util.go | 4 ++-- 7 files changed, 30 insertions(+), 28 deletions(-) rename routing/{dht => kbucket}/bucket.go (96%) rename routing/{dht => kbucket}/table.go (97%) rename routing/{dht => kbucket}/table_test.go (83%) rename routing/{dht => kbucket}/util.go (95%) diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 8fbd5c092..7188fc893 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -10,6 +10,7 @@ import ( peer "github.com/jbenet/go-ipfs/peer" swarm "github.com/jbenet/go-ipfs/swarm" u "github.com/jbenet/go-ipfs/util" + kb "github.com/jbenet/go-ipfs/routing/kbucket" ma "github.com/jbenet/go-multiaddr" @@ -25,7 +26,7 @@ import ( type IpfsDHT struct { // Array of routing tables for differently distanced nodes // NOTE: (currently, only a single table is used) - routes []*RoutingTable + routes []*kb.RoutingTable network *swarm.Swarm @@ -84,8 +85,8 @@ func NewDHT(p *peer.Peer) (*IpfsDHT, error) { dht.listeners = make(map[uint64]*listenInfo) dht.providers = make(map[u.Key][]*providerInfo) dht.shutdown = make(chan struct{}) - dht.routes = make([]*RoutingTable, 1) - dht.routes[0] = NewRoutingTable(20, convertPeerID(p.ID)) + dht.routes = make([]*kb.RoutingTable, 1) + dht.routes[0] = kb.NewRoutingTable(20, kb.ConvertPeerID(p.ID)) dht.birth = time.Now() return dht, nil } @@ -253,7 +254,7 @@ func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *PBDHTMessage) { } } else if err == ds.ErrNotFound { // Find closest peer(s) to desired key and reply with that info - closer := dht.routes[0].NearestPeer(convertKey(u.Key(pmes.GetKey()))) + closer := dht.routes[0].NearestPeer(kb.ConvertKey(u.Key(pmes.GetKey()))) resp = &DHTMessage{ Response: true, Id: *pmes.Id, @@ -290,7 +291,7 @@ func (dht *IpfsDHT) handlePing(p *peer.Peer, pmes *PBDHTMessage) { func (dht *IpfsDHT) handleFindPeer(p *peer.Peer, pmes *PBDHTMessage) { success := true u.POut("handleFindPeer: searching for '%s'", peer.ID(pmes.GetKey()).Pretty()) - closest := dht.routes[0].NearestPeer(convertKey(u.Key(pmes.GetKey()))) + closest := dht.routes[0].NearestPeer(kb.ConvertKey(u.Key(pmes.GetKey()))) if closest == nil { u.PErr("handleFindPeer: could not find anything.") success = false @@ -432,7 +433,7 @@ func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *PBDHTMessage) { } dht.diaglock.Unlock() - seq := dht.routes[0].NearestPeers(convertPeerID(dht.self.ID), 10) + seq := dht.routes[0].NearestPeers(kb.ConvertPeerID(dht.self.ID), 10) listen_chan := dht.ListenFor(pmes.GetId(), len(seq), time.Second*30) for _, ps := range seq { diff --git a/routing/dht/diag.go b/routing/dht/diag.go index 4bc752f92..50d5a3d50 100644 --- a/routing/dht/diag.go +++ b/routing/dht/diag.go @@ -37,7 +37,7 @@ func (dht *IpfsDHT) getDiagInfo() *diagInfo { di.LifeSpan = time.Since(dht.birth) di.Keys = nil // Currently no way to query datastore - for _,p := range dht.routes[0].listpeers() { + for _,p := range dht.routes[0].Listpeers() { di.Connections = append(di.Connections, connDiagInfo{p.GetLatency(), p.ID}) } return di diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 57d087fdc..8898aaf15 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -12,6 +12,7 @@ import ( ma "github.com/jbenet/go-multiaddr" peer "github.com/jbenet/go-ipfs/peer" + kb "github.com/jbenet/go-ipfs/routing/kbucket" swarm "github.com/jbenet/go-ipfs/swarm" u "github.com/jbenet/go-ipfs/util" ) @@ -33,7 +34,7 @@ func GenerateMessageID() uint64 { // This is the top level "Store" operation of the DHT func (s *IpfsDHT) PutValue(key u.Key, value []byte) error { var p *peer.Peer - p = s.routes[0].NearestPeer(convertKey(key)) + p = s.routes[0].NearestPeer(kb.ConvertKey(key)) if p == nil { return errors.New("Table returned nil peer!") } @@ -46,7 +47,7 @@ func (s *IpfsDHT) PutValue(key u.Key, value []byte) error { // returned along with util.ErrSearchIncomplete func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { var p *peer.Peer - p = s.routes[0].NearestPeer(convertKey(key)) + p = s.routes[0].NearestPeer(kb.ConvertKey(key)) if p == nil { return nil, errors.New("Table returned nil peer!") } @@ -90,7 +91,7 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { // Announce that this node can provide value for given key func (s *IpfsDHT) Provide(key u.Key) error { - peers := s.routes[0].NearestPeers(convertKey(key), PoolSize) + peers := s.routes[0].NearestPeers(kb.ConvertKey(key), PoolSize) if len(peers) == 0 { //return an error } @@ -110,7 +111,7 @@ func (s *IpfsDHT) Provide(key u.Key) error { // FindProviders searches for peers who can provide the value for given key. func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, error) { - p := s.routes[0].NearestPeer(convertKey(key)) + p := s.routes[0].NearestPeer(kb.ConvertKey(key)) pmes := DHTMessage{ Type: PBDHTMessage_GET_PROVIDERS, @@ -168,7 +169,7 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, // FindPeer searches for a peer with given ID. func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error) { - p := s.routes[0].NearestPeer(convertPeerID(id)) + p := s.routes[0].NearestPeer(kb.ConvertPeerID(id)) pmes := DHTMessage{ Type: PBDHTMessage_FIND_NODE, @@ -242,7 +243,7 @@ func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { func (dht *IpfsDHT) GetDiagnostic(timeout time.Duration) ([]*diagInfo, error) { u.DOut("Begin Diagnostic") //Send to N closest peers - targets := dht.routes[0].NearestPeers(convertPeerID(dht.self.ID), 10) + targets := dht.routes[0].NearestPeers(kb.ConvertPeerID(dht.self.ID), 10) // TODO: Add timeout to this struct so nodes know when to return pmes := DHTMessage{ diff --git a/routing/dht/bucket.go b/routing/kbucket/bucket.go similarity index 96% rename from routing/dht/bucket.go rename to routing/kbucket/bucket.go index 7aa8d0a94..a56db74fe 100644 --- a/routing/dht/bucket.go +++ b/routing/kbucket/bucket.go @@ -48,7 +48,7 @@ func (b *Bucket) Split(cpl int, target ID) *Bucket { out := list.New() e := bucket_list.Front() for e != nil { - peer_id := convertPeerID(e.Value.(*peer.Peer).ID) + peer_id := ConvertPeerID(e.Value.(*peer.Peer).ID) peer_cpl := prefLen(peer_id, target) if peer_cpl > cpl { cur := e diff --git a/routing/dht/table.go b/routing/kbucket/table.go similarity index 97% rename from routing/dht/table.go rename to routing/kbucket/table.go index 7a121234f..de971ea21 100644 --- a/routing/dht/table.go +++ b/routing/kbucket/table.go @@ -36,7 +36,7 @@ func NewRoutingTable(bucketsize int, local_id ID) *RoutingTable { func (rt *RoutingTable) Update(p *peer.Peer) *peer.Peer { rt.tabLock.Lock() defer rt.tabLock.Unlock() - peer_id := convertPeerID(p.ID) + peer_id := ConvertPeerID(p.ID) cpl := xor(peer_id, rt.local).commonPrefixLen() b_id := cpl @@ -97,7 +97,7 @@ func (p peerSorterArr) Less(a, b int) bool { func copyPeersFromList(target ID, peerArr peerSorterArr, peerList *list.List) peerSorterArr { for e := peerList.Front(); e != nil; e = e.Next() { p := e.Value.(*peer.Peer) - p_id := convertPeerID(p.ID) + p_id := ConvertPeerID(p.ID) pd := peerDistance{ p: p, distance: xor(target, p_id), @@ -173,7 +173,7 @@ func (rt *RoutingTable) Size() int { } // NOTE: This is potentially unsafe... use at your own risk -func (rt *RoutingTable) listpeers() []*peer.Peer { +func (rt *RoutingTable) Listpeers() []*peer.Peer { var peers []*peer.Peer for _,buck := range rt.Buckets { for e := buck.getIter(); e != nil; e = e.Next() { diff --git a/routing/dht/table_test.go b/routing/kbucket/table_test.go similarity index 83% rename from routing/dht/table_test.go rename to routing/kbucket/table_test.go index 393a1c585..4305d8d1f 100644 --- a/routing/dht/table_test.go +++ b/routing/kbucket/table_test.go @@ -36,7 +36,7 @@ func TestBucket(t *testing.T) { } local := _randPeer() - local_id := convertPeerID(local.ID) + local_id := ConvertPeerID(local.ID) i := rand.Intn(len(peers)) e := b.Find(peers[i].ID) @@ -44,10 +44,10 @@ func TestBucket(t *testing.T) { t.Errorf("Failed to find peer: %v", peers[i]) } - spl := b.Split(0, convertPeerID(local.ID)) + spl := b.Split(0, ConvertPeerID(local.ID)) llist := (*list.List)(b) for e := llist.Front(); e != nil; e = e.Next() { - p := convertPeerID(e.Value.(*peer.Peer).ID) + p := ConvertPeerID(e.Value.(*peer.Peer).ID) cpl := xor(p, local_id).commonPrefixLen() if cpl > 0 { t.Fatalf("Split failed. found id with cpl > 0 in 0 bucket") @@ -56,7 +56,7 @@ func TestBucket(t *testing.T) { rlist := (*list.List)(spl) for e := rlist.Front(); e != nil; e = e.Next() { - p := convertPeerID(e.Value.(*peer.Peer).ID) + p := ConvertPeerID(e.Value.(*peer.Peer).ID) cpl := xor(p, local_id).commonPrefixLen() if cpl == 0 { t.Fatalf("Split failed. found id with cpl == 0 in non 0 bucket") @@ -67,7 +67,7 @@ func TestBucket(t *testing.T) { // Right now, this just makes sure that it doesnt hang or crash func TestTableUpdate(t *testing.T) { local := _randPeer() - rt := NewRoutingTable(10, convertPeerID(local.ID)) + rt := NewRoutingTable(10, ConvertPeerID(local.ID)) peers := make([]*peer.Peer, 100) for i := 0; i < 100; i++ { @@ -93,7 +93,7 @@ func TestTableUpdate(t *testing.T) { func TestTableFind(t *testing.T) { local := _randPeer() - rt := NewRoutingTable(10, convertPeerID(local.ID)) + rt := NewRoutingTable(10, ConvertPeerID(local.ID)) peers := make([]*peer.Peer, 100) for i := 0; i < 5; i++ { @@ -102,7 +102,7 @@ func TestTableFind(t *testing.T) { } t.Logf("Searching for peer: '%s'", peers[2].ID.Pretty()) - found := rt.NearestPeer(convertPeerID(peers[2].ID)) + found := rt.NearestPeer(ConvertPeerID(peers[2].ID)) if !found.ID.Equal(peers[2].ID) { t.Fatalf("Failed to lookup known node...") } @@ -110,7 +110,7 @@ func TestTableFind(t *testing.T) { func TestTableFindMultiple(t *testing.T) { local := _randPeer() - rt := NewRoutingTable(20, convertPeerID(local.ID)) + rt := NewRoutingTable(20, ConvertPeerID(local.ID)) peers := make([]*peer.Peer, 100) for i := 0; i < 18; i++ { @@ -119,7 +119,7 @@ func TestTableFindMultiple(t *testing.T) { } t.Logf("Searching for peer: '%s'", peers[2].ID.Pretty()) - found := rt.NearestPeers(convertPeerID(peers[2].ID), 15) + found := rt.NearestPeers(ConvertPeerID(peers[2].ID), 15) if len(found) != 15 { t.Fatalf("Got back different number of peers than we expected.") } diff --git a/routing/dht/util.go b/routing/kbucket/util.go similarity index 95% rename from routing/dht/util.go rename to routing/kbucket/util.go index 5961cd226..095d0b03e 100644 --- a/routing/dht/util.go +++ b/routing/kbucket/util.go @@ -71,12 +71,12 @@ func equalizeSizes(a, b ID) (ID, ID) { return a, b } -func convertPeerID(id peer.ID) ID { +func ConvertPeerID(id peer.ID) ID { hash := sha256.Sum256(id) return hash[:] } -func convertKey(id u.Key) ID { +func ConvertKey(id u.Key) ID { hash := sha256.Sum256([]byte(id)) return hash[:] } From 67ddab1e4e7003a2a33bd9f9e38bc8aa0b5d9632 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Sat, 9 Aug 2014 22:28:46 -0700 Subject: [PATCH 21/30] tiered put/get implemented --- peer/peer.go | 6 +- routing/dht/DHTMessage.go | 21 ++++++ routing/dht/dht.go | 137 ++++++++++++++++++++++++++++------ routing/dht/dht_test.go | 84 ++++++++++++++++++++- routing/dht/diag.go | 11 ++- routing/dht/routing.go | 87 ++++++++++----------- routing/kbucket/bucket.go | 1 + routing/kbucket/table.go | 35 ++++++--- routing/kbucket/table_test.go | 2 +- swarm/swarm_test.go | 2 +- 10 files changed, 288 insertions(+), 98 deletions(-) diff --git a/peer/peer.go b/peer/peer.go index f09c4c03d..a54179b3b 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -1,13 +1,13 @@ package peer import ( - "time" "sync" + "time" + b58 "github.com/jbenet/go-base58" u "github.com/jbenet/go-ipfs/util" ma "github.com/jbenet/go-multiaddr" mh "github.com/jbenet/go-multihash" - b58 "github.com/jbenet/go-base58" "bytes" ) @@ -33,7 +33,7 @@ type Peer struct { ID ID Addresses []*ma.Multiaddr - latency time.Duration + latency time.Duration latenLock sync.RWMutex } diff --git a/routing/dht/DHTMessage.go b/routing/dht/DHTMessage.go index 64ca5bcab..701f36687 100644 --- a/routing/dht/DHTMessage.go +++ b/routing/dht/DHTMessage.go @@ -1,5 +1,9 @@ package dht +import ( + peer "github.com/jbenet/go-ipfs/peer" +) + // A helper struct to make working with protbuf types easier type DHTMessage struct { Type PBDHTMessage_MessageType @@ -8,6 +12,20 @@ type DHTMessage struct { Response bool Id uint64 Success bool + Peers []*peer.Peer +} + +func peerInfo(p *peer.Peer) *PBDHTMessage_PBPeer { + pbp := new(PBDHTMessage_PBPeer) + addr, err := p.Addresses[0].String() + if err != nil { + //Temp: what situations could cause this? + panic(err) + } + pbp.Addr = &addr + pid := string(p.ID) + pbp.Id = &pid + return pbp } func (m *DHTMessage) ToProtobuf() *PBDHTMessage { @@ -21,6 +39,9 @@ func (m *DHTMessage) ToProtobuf() *PBDHTMessage { pmes.Response = &m.Response pmes.Id = &m.Id pmes.Success = &m.Success + for _, p := range m.Peers { + pmes.Peers = append(pmes.Peers, peerInfo(p)) + } return pmes } diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 7188fc893..10954d2ba 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -8,9 +8,9 @@ import ( "time" peer "github.com/jbenet/go-ipfs/peer" + kb "github.com/jbenet/go-ipfs/routing/kbucket" swarm "github.com/jbenet/go-ipfs/swarm" u "github.com/jbenet/go-ipfs/util" - kb "github.com/jbenet/go-ipfs/routing/kbucket" ma "github.com/jbenet/go-multiaddr" @@ -37,7 +37,6 @@ type IpfsDHT struct { datastore ds.Datastore // Map keys to peers that can provide their value - // TODO: implement a TTL on each of these keys providers map[u.Key][]*providerInfo providerLock sync.RWMutex @@ -67,7 +66,7 @@ type listenInfo struct { eol time.Time } -// Create a new DHT object with the given peer as the 'local' host +// NewDHT creates a new DHT object with the given peer as the 'local' host func NewDHT(p *peer.Peer) (*IpfsDHT, error) { if p == nil { return nil, errors.New("nil peer passed to NewDHT()") @@ -111,7 +110,7 @@ func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { // NOTE: this should be done better... err = dht.Ping(npeer, time.Second*2) if err != nil { - return nil, errors.New("Failed to ping newly connected peer.") + return nil, errors.New("failed to ping newly connected peer") } return npeer, nil @@ -227,7 +226,7 @@ func (dht *IpfsDHT) cleanExpiredListeners() { dht.listenLock.Unlock() } -func (dht *IpfsDHT) putValueToPeer(p *peer.Peer, key string, value []byte) error { +func (dht *IpfsDHT) putValueToNetwork(p *peer.Peer, key string, value []byte) error { pmes := DHTMessage{ Type: PBDHTMessage_PUT_VALUE, Key: key, @@ -242,26 +241,32 @@ func (dht *IpfsDHT) putValueToPeer(p *peer.Peer, key string, value []byte) error func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *PBDHTMessage) { dskey := ds.NewKey(pmes.GetKey()) - var resp *DHTMessage - i_val, err := dht.datastore.Get(dskey) + resp := &DHTMessage{ + Response: true, + Id: pmes.GetId(), + Key: pmes.GetKey(), + } + iVal, err := dht.datastore.Get(dskey) if err == nil { - resp = &DHTMessage{ - Response: true, - Id: *pmes.Id, - Key: *pmes.Key, - Value: i_val.([]byte), - Success: true, - } + resp.Success = true + resp.Value = iVal.([]byte) } else if err == ds.ErrNotFound { - // Find closest peer(s) to desired key and reply with that info - closer := dht.routes[0].NearestPeer(kb.ConvertKey(u.Key(pmes.GetKey()))) - resp = &DHTMessage{ - Response: true, - Id: *pmes.Id, - Key: *pmes.Key, - Value: closer.ID, - Success: false, + // Check if we know any providers for the requested value + provs, ok := dht.providers[u.Key(pmes.GetKey())] + if ok && len(provs) > 0 { + for _, prov := range provs { + resp.Peers = append(resp.Peers, prov.Value) + } + resp.Success = true + } else { + // No providers? + // Find closest peer(s) to desired key and reply with that info + closer := dht.routes[0].NearestPeer(kb.ConvertKey(u.Key(pmes.GetKey()))) + resp.Peers = []*peer.Peer{closer} } + } else { + //temp: what other errors can a datastore throw? + panic(err) } mes := swarm.NewMessage(p, resp.ToProtobuf()) @@ -397,6 +402,7 @@ func (dht *IpfsDHT) Unlisten(mesid uint64) { close(list.resp) } +// Check whether or not the dht is currently listening for mesid func (dht *IpfsDHT) IsListening(mesid uint64) bool { dht.listenLock.RLock() li, ok := dht.listeners[mesid] @@ -424,6 +430,7 @@ func (dht *IpfsDHT) addProviderEntry(key u.Key, p *peer.Peer) { dht.providerLock.Unlock() } +// NOTE: not yet finished, low priority func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *PBDHTMessage) { dht.diaglock.Lock() if dht.IsListening(pmes.GetId()) { @@ -434,7 +441,7 @@ func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *PBDHTMessage) { dht.diaglock.Unlock() seq := dht.routes[0].NearestPeers(kb.ConvertPeerID(dht.self.ID), 10) - listen_chan := dht.ListenFor(pmes.GetId(), len(seq), time.Second*30) + listenChan := dht.ListenFor(pmes.GetId(), len(seq), time.Second*30) for _, ps := range seq { mes := swarm.NewMessage(ps, pmes) @@ -453,7 +460,7 @@ func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *PBDHTMessage) { case <-after: //Timeout, return what we have goto out - case req_resp := <-listen_chan: + case req_resp := <-listenChan: pmes_out := new(PBDHTMessage) err := proto.Unmarshal(req_resp.Data, pmes_out) if err != nil { @@ -477,6 +484,77 @@ out: dht.network.Chan.Outgoing <- mes } +func (dht *IpfsDHT) getValueSingle(p *peer.Peer, key u.Key, timeout time.Duration) ([]byte, error) { + pmes := DHTMessage{ + Type: PBDHTMessage_GET_VALUE, + Key: string(key), + Id: GenerateMessageID(), + } + response_chan := dht.ListenFor(pmes.Id, 1, time.Minute) + + mes := swarm.NewMessage(p, pmes.ToProtobuf()) + dht.network.Chan.Outgoing <- mes + + // Wait for either the response or a timeout + timeup := time.After(timeout) + select { + case <-timeup: + dht.Unlisten(pmes.Id) + return nil, u.ErrTimeout + case resp, ok := <-response_chan: + if !ok { + u.PErr("response channel closed before timeout, please investigate.") + return nil, u.ErrTimeout + } + pmes_out := new(PBDHTMessage) + err := proto.Unmarshal(resp.Data, pmes_out) + if err != nil { + return nil, err + } + // TODO: debate moving this logic out of this function to be handled by the caller + if pmes_out.GetSuccess() { + if pmes_out.Value == nil { + // We were given provider[s] + return dht.getFromProviderList(key, timeout, pmes_out.GetPeers()) + } + // We were given the value + return pmes_out.GetValue(), nil + } else { + return pmes_out.GetValue(), u.ErrSearchIncomplete + } + } +} + +// TODO: Im not certain on this implementation, we get a list of providers from someone +// what do we do with it? Connect to each of them? randomly pick one to get the value from? +// Or just connect to one at a time until we get a successful connection and request the +// value from it? +func (dht *IpfsDHT) getFromProviderList(key u.Key, timeout time.Duration, provlist []*PBDHTMessage_PBPeer) ([]byte, error) { + for _, prov := range provlist { + prov_p, _ := dht.Find(peer.ID(prov.GetId())) + if prov_p == nil { + maddr, err := ma.NewMultiaddr(prov.GetAddr()) + if err != nil { + u.PErr("getValue error: %s", err) + continue + } + prov_p, err = dht.Connect(maddr) + if err != nil { + u.PErr("getValue error: %s", err) + continue + } + } + data, err := dht.getValueSingle(prov_p, key, timeout) + if err != nil { + u.DErr("getFromProvs error: %s", err) + continue + } + + return data, nil + } + return nil, u.ErrNotFound +} + func (dht *IpfsDHT) GetLocal(key u.Key) ([]byte, error) { v, err := dht.datastore.Get(ds.NewKey(string(key))) if err != nil { @@ -495,3 +573,14 @@ func (dht *IpfsDHT) Update(p *peer.Peer) { dht.network.Drop(removed) } } + +// Look for a peer with a given ID connected to this dht +func (dht *IpfsDHT) Find(id peer.ID) (*peer.Peer, *kb.RoutingTable) { + for _, table := range dht.routes { + p := table.Find(id) + if p != nil { + return p, table + } + } + return nil, nil +} diff --git a/routing/dht/dht_test.go b/routing/dht/dht_test.go index a5b47cb21..177128978 100644 --- a/routing/dht/dht_test.go +++ b/routing/dht/dht_test.go @@ -90,15 +90,21 @@ func TestValueGetSet(t *testing.T) { dht_a.Start() dht_b.Start() + go func() { + select { + case err := <-dht_a.network.Chan.Errors: + t.Fatal(err) + case err := <-dht_b.network.Chan.Errors: + t.Fatal(err) + } + }() + _, err = dht_a.Connect(addr_b) if err != nil { t.Fatal(err) } - err = dht_a.PutValue("hello", []byte("world")) - if err != nil { - t.Fatal(err) - } + dht_a.PutValue("hello", []byte("world")) val, err := dht_a.GetValue("hello", time.Second*2) if err != nil { @@ -179,3 +185,73 @@ func TestProvides(t *testing.T) { dhts[i].Halt() } } + +func TestLayeredGet(t *testing.T) { + u.Debug = false + var addrs []*ma.Multiaddr + for i := 0; i < 4; i++ { + a, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 5000+i)) + if err != nil { + t.Fatal(err) + } + addrs = append(addrs, a) + } + + var peers []*peer.Peer + for i := 0; i < 4; i++ { + p := new(peer.Peer) + p.AddAddress(addrs[i]) + p.ID = peer.ID([]byte(fmt.Sprintf("peer_%d", i))) + peers = append(peers, p) + } + + var dhts []*IpfsDHT + for i := 0; i < 4; i++ { + d, err := NewDHT(peers[i]) + if err != nil { + t.Fatal(err) + } + dhts = append(dhts, d) + d.Start() + } + + _, err := dhts[0].Connect(addrs[1]) + if err != nil { + t.Fatal(err) + } + + _, err = dhts[1].Connect(addrs[2]) + if err != nil { + t.Fatal(err) + } + + _, err = dhts[1].Connect(addrs[3]) + if err != nil { + t.Fatal(err) + } + + err = dhts[3].PutLocal(u.Key("hello"), []byte("world")) + if err != nil { + t.Fatal(err) + } + + err = dhts[3].Provide(u.Key("hello")) + if err != nil { + t.Fatal(err) + } + + time.Sleep(time.Millisecond * 60) + + val, err := dhts[0].GetValue(u.Key("hello"), time.Second) + if err != nil { + t.Fatal(err) + } + + if string(val) != "world" { + t.Fatal("Got incorrect value.") + } + + for i := 0; i < 4; i++ { + dhts[i].Halt() + } +} diff --git a/routing/dht/diag.go b/routing/dht/diag.go index 50d5a3d50..03997c5e7 100644 --- a/routing/dht/diag.go +++ b/routing/dht/diag.go @@ -9,14 +9,14 @@ import ( type connDiagInfo struct { Latency time.Duration - Id peer.ID + Id peer.ID } type diagInfo struct { - Id peer.ID + Id peer.ID Connections []connDiagInfo - Keys []string - LifeSpan time.Duration + Keys []string + LifeSpan time.Duration CodeVersion string } @@ -29,7 +29,6 @@ func (di *diagInfo) Marshal() []byte { return b } - func (dht *IpfsDHT) getDiagInfo() *diagInfo { di := new(diagInfo) di.CodeVersion = "github.com/jbenet/go-ipfs" @@ -37,7 +36,7 @@ func (dht *IpfsDHT) getDiagInfo() *diagInfo { di.LifeSpan = time.Since(dht.birth) di.Keys = nil // Currently no way to query datastore - for _,p := range dht.routes[0].Listpeers() { + for _, p := range dht.routes[0].Listpeers() { di.Connections = append(di.Connections, connDiagInfo{p.GetLatency(), p.ID}) } return di diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 8898aaf15..2c6a9b74c 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "math/rand" "time" @@ -32,58 +33,50 @@ func GenerateMessageID() uint64 { // PutValue adds value corresponding to given Key. // This is the top level "Store" operation of the DHT -func (s *IpfsDHT) PutValue(key u.Key, value []byte) error { - var p *peer.Peer - p = s.routes[0].NearestPeer(kb.ConvertKey(key)) - if p == nil { - return errors.New("Table returned nil peer!") +func (s *IpfsDHT) PutValue(key u.Key, value []byte) { + complete := make(chan struct{}) + for i, route := range s.routes { + p := route.NearestPeer(kb.ConvertKey(key)) + if p == nil { + s.network.Chan.Errors <- fmt.Errorf("No peer found on level %d", i) + continue + go func() { + complete <- struct{}{} + }() + } + go func() { + err := s.putValueToNetwork(p, string(key), value) + if err != nil { + s.network.Chan.Errors <- err + } + complete <- struct{}{} + }() + } + for _, _ = range s.routes { + <-complete } - - return s.putValueToPeer(p, string(key), value) } // GetValue searches for the value corresponding to given Key. // If the search does not succeed, a multiaddr string of a closer peer is // returned along with util.ErrSearchIncomplete func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { - var p *peer.Peer - p = s.routes[0].NearestPeer(kb.ConvertKey(key)) - if p == nil { - return nil, errors.New("Table returned nil peer!") - } - - pmes := DHTMessage{ - Type: PBDHTMessage_GET_VALUE, - Key: string(key), - Id: GenerateMessageID(), - } - response_chan := s.ListenFor(pmes.Id, 1, time.Minute) - - mes := swarm.NewMessage(p, pmes.ToProtobuf()) - s.network.Chan.Outgoing <- mes - - // Wait for either the response or a timeout - timeup := time.After(timeout) - select { - case <-timeup: - s.Unlisten(pmes.Id) - return nil, u.ErrTimeout - case resp, ok := <-response_chan: - if !ok { - u.PErr("response channel closed before timeout, please investigate.") - return nil, u.ErrTimeout + for _, route := range s.routes { + var p *peer.Peer + p = route.NearestPeer(kb.ConvertKey(key)) + if p == nil { + return nil, errors.New("Table returned nil peer!") } - pmes_out := new(PBDHTMessage) - err := proto.Unmarshal(resp.Data, pmes_out) - if err != nil { + + b, err := s.getValueSingle(p, key, timeout) + if err == nil { + return b, nil + } + if err != u.ErrSearchIncomplete { return nil, err } - if pmes_out.GetSuccess() { - return pmes_out.GetValue(), nil - } else { - return pmes_out.GetValue(), u.ErrSearchIncomplete - } } + return nil, u.ErrNotFound } // Value provider layer of indirection. @@ -121,7 +114,7 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, mes := swarm.NewMessage(p, pmes.ToProtobuf()) - listen_chan := s.ListenFor(pmes.Id, 1, time.Minute) + listenChan := s.ListenFor(pmes.Id, 1, time.Minute) u.DOut("Find providers for: '%s'", key) s.network.Chan.Outgoing <- mes after := time.After(timeout) @@ -129,7 +122,7 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, case <-after: s.Unlisten(pmes.Id) return nil, u.ErrTimeout - case resp := <-listen_chan: + case resp := <-listenChan: u.DOut("FindProviders: got response.") pmes_out := new(PBDHTMessage) err := proto.Unmarshal(resp.Data, pmes_out) @@ -179,14 +172,14 @@ func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error mes := swarm.NewMessage(p, pmes.ToProtobuf()) - listen_chan := s.ListenFor(pmes.Id, 1, time.Minute) + listenChan := s.ListenFor(pmes.Id, 1, time.Minute) s.network.Chan.Outgoing <- mes after := time.After(timeout) select { case <-after: s.Unlisten(pmes.Id) return nil, u.ErrTimeout - case resp := <-listen_chan: + case resp := <-listenChan: pmes_out := new(PBDHTMessage) err := proto.Unmarshal(resp.Data, pmes_out) if err != nil { @@ -251,7 +244,7 @@ func (dht *IpfsDHT) GetDiagnostic(timeout time.Duration) ([]*diagInfo, error) { Id: GenerateMessageID(), } - listen_chan := dht.ListenFor(pmes.Id, len(targets), time.Minute*2) + listenChan := dht.ListenFor(pmes.Id, len(targets), time.Minute*2) pbmes := pmes.ToProtobuf() for _, p := range targets { @@ -266,7 +259,7 @@ func (dht *IpfsDHT) GetDiagnostic(timeout time.Duration) ([]*diagInfo, error) { case <-after: u.DOut("Diagnostic request timed out.") return out, u.ErrTimeout - case resp := <-listen_chan: + case resp := <-listenChan: pmes_out := new(PBDHTMessage) err := proto.Unmarshal(resp.Data, pmes_out) if err != nil { diff --git a/routing/kbucket/bucket.go b/routing/kbucket/bucket.go index a56db74fe..5abd2c910 100644 --- a/routing/kbucket/bucket.go +++ b/routing/kbucket/bucket.go @@ -5,6 +5,7 @@ import ( peer "github.com/jbenet/go-ipfs/peer" ) + // Bucket holds a list of peers. type Bucket list.List diff --git a/routing/kbucket/table.go b/routing/kbucket/table.go index de971ea21..788d12265 100644 --- a/routing/kbucket/table.go +++ b/routing/kbucket/table.go @@ -19,7 +19,7 @@ type RoutingTable struct { tabLock sync.RWMutex // kBuckets define all the fingers to other nodes. - Buckets []*Bucket + Buckets []*Bucket bucketsize int } @@ -52,7 +52,7 @@ func (rt *RoutingTable) Update(p *peer.Peer) *peer.Peer { // Are we past the max bucket size? if bucket.Len() > rt.bucketsize { - if b_id == len(rt.Buckets) - 1 { + if b_id == len(rt.Buckets)-1 { new_bucket := bucket.Split(b_id, rt.local) rt.Buckets = append(rt.Buckets, new_bucket) if new_bucket.Len() > rt.bucketsize { @@ -81,25 +81,27 @@ func (rt *RoutingTable) Update(p *peer.Peer) *peer.Peer { // A helper struct to sort peers by their distance to the local node type peerDistance struct { - p *peer.Peer + p *peer.Peer distance ID } // peerSorterArr implements sort.Interface to sort peers by xor distance type peerSorterArr []*peerDistance -func (p peerSorterArr) Len() int {return len(p)} -func (p peerSorterArr) Swap(a, b int) {p[a],p[b] = p[b],p[a]} + +func (p peerSorterArr) Len() int { return len(p) } +func (p peerSorterArr) Swap(a, b int) { p[a], p[b] = p[b], p[a] } func (p peerSorterArr) Less(a, b int) bool { return p[a].distance.Less(p[b].distance) } + // func copyPeersFromList(target ID, peerArr peerSorterArr, peerList *list.List) peerSorterArr { - for e := peerList.Front(); e != nil; e = e.Next() { + for e := peerList.Front(); e != nil; e = e.Next() { p := e.Value.(*peer.Peer) p_id := ConvertPeerID(p.ID) pd := peerDistance{ - p: p, + p: p, distance: xor(target, p_id), } peerArr = append(peerArr, &pd) @@ -111,6 +113,15 @@ func copyPeersFromList(target ID, peerArr peerSorterArr, peerList *list.List) pe return peerArr } +// Find a specific peer by ID or return nil +func (rt *RoutingTable) Find(id peer.ID) *peer.Peer { + srch := rt.NearestPeers(ConvertPeerID(id), 1) + if len(srch) == 0 || !srch[0].ID.Equal(id) { + return nil + } + return srch[0] +} + // Returns a single peer that is nearest to the given ID func (rt *RoutingTable) NearestPeer(id ID) *peer.Peer { peers := rt.NearestPeers(id, 1) @@ -139,12 +150,12 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { // In the case of an unusual split, one bucket may be empty. // if this happens, search both surrounding buckets for nearest peer if cpl > 0 { - plist := (*list.List)(rt.Buckets[cpl - 1]) + plist := (*list.List)(rt.Buckets[cpl-1]) peerArr = copyPeersFromList(id, peerArr, plist) } - if cpl < len(rt.Buckets) - 1 { - plist := (*list.List)(rt.Buckets[cpl + 1]) + if cpl < len(rt.Buckets)-1 { + plist := (*list.List)(rt.Buckets[cpl+1]) peerArr = copyPeersFromList(id, peerArr, plist) } } else { @@ -166,7 +177,7 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { // Returns the total number of peers in the routing table func (rt *RoutingTable) Size() int { var tot int - for _,buck := range rt.Buckets { + for _, buck := range rt.Buckets { tot += buck.Len() } return tot @@ -175,7 +186,7 @@ func (rt *RoutingTable) Size() int { // NOTE: This is potentially unsafe... use at your own risk func (rt *RoutingTable) Listpeers() []*peer.Peer { var peers []*peer.Peer - for _,buck := range rt.Buckets { + for _, buck := range rt.Buckets { for e := buck.getIter(); e != nil; e = e.Next() { peers = append(peers, e.Value.(*peer.Peer)) } diff --git a/routing/kbucket/table_test.go b/routing/kbucket/table_test.go index 4305d8d1f..842c92510 100644 --- a/routing/kbucket/table_test.go +++ b/routing/kbucket/table_test.go @@ -1,10 +1,10 @@ package dht import ( + "container/list" crand "crypto/rand" "crypto/sha256" "math/rand" - "container/list" "testing" peer "github.com/jbenet/go-ipfs/peer" diff --git a/swarm/swarm_test.go b/swarm/swarm_test.go index 4acfe1b71..b23919ecc 100644 --- a/swarm/swarm_test.go +++ b/swarm/swarm_test.go @@ -44,7 +44,7 @@ func TestSwarm(t *testing.T) { swarm := NewSwarm(nil) var peers []*peer.Peer - var listeners []net.Listener + var listeners []net.Listener peerNames := map[string]string{ "11140beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a30": "/ip4/127.0.0.1/tcp/1234", "11140beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a31": "/ip4/127.0.0.1/tcp/2345", From a43886245e7e5ada38acda3d8815111e57d8c9d3 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Sun, 10 Aug 2014 21:02:05 -0700 Subject: [PATCH 22/30] more work implementing coral type lookups --- routing/dht/dht.go | 213 +++++++++++++++++++++------------------- routing/dht/dht_test.go | 127 ++++++++++++++++++------ routing/dht/routing.go | 139 ++++++++++++++------------ routing/kbucket/util.go | 20 +++- swarm/swarm.go | 14 +++ util/util.go | 26 +++++ 6 files changed, 346 insertions(+), 193 deletions(-) diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 10954d2ba..388ab9ab1 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -2,7 +2,6 @@ package dht import ( "bytes" - "encoding/json" "errors" "sync" "time" @@ -28,7 +27,7 @@ type IpfsDHT struct { // NOTE: (currently, only a single table is used) routes []*kb.RoutingTable - network *swarm.Swarm + network swarm.Network // Local peer (yourself) self *peer.Peer @@ -95,7 +94,7 @@ func (dht *IpfsDHT) Start() { go dht.handleMessages() } -// Connect to a new peer at the given address +// Connect to a new peer at the given address, ping and add to the routing table func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { maddrstr, _ := addr.String() u.DOut("Connect to new peer: %s", maddrstr) @@ -104,8 +103,6 @@ func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { return nil, err } - dht.Update(npeer) - // Ping new peer to register in their routing table // NOTE: this should be done better... err = dht.Ping(npeer, time.Second*2) @@ -113,6 +110,8 @@ func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { return nil, errors.New("failed to ping newly connected peer") } + dht.Update(npeer) + return npeer, nil } @@ -122,9 +121,10 @@ func (dht *IpfsDHT) handleMessages() { u.DOut("Begin message handling routine") checkTimeouts := time.NewTicker(time.Minute * 5) + ch := dht.network.GetChan() for { select { - case mes, ok := <-dht.network.Chan.Incoming: + case mes, ok := <-ch.Incoming: if !ok { u.DOut("handleMessages closing, bad recv on incoming") return @@ -184,8 +184,8 @@ func (dht *IpfsDHT) handleMessages() { dht.handleDiagnostic(mes.Peer, pmes) } - case err := <-dht.network.Chan.Errors: - u.DErr("dht err: %s", err) + case err := <-ch.Errors: + u.PErr("dht err: %s", err) case <-dht.shutdown: checkTimeouts.Stop() return @@ -235,7 +235,7 @@ func (dht *IpfsDHT) putValueToNetwork(p *peer.Peer, key string, value []byte) er } mes := swarm.NewMessage(p, pmes.ToProtobuf()) - dht.network.Chan.Outgoing <- mes + dht.network.Send(mes) return nil } @@ -260,17 +260,26 @@ func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *PBDHTMessage) { resp.Success = true } else { // No providers? - // Find closest peer(s) to desired key and reply with that info - closer := dht.routes[0].NearestPeer(kb.ConvertKey(u.Key(pmes.GetKey()))) - resp.Peers = []*peer.Peer{closer} + // Find closest peer on given cluster to desired key and reply with that info + + level := pmes.GetValue()[0] // Using value field to specify cluster level + + closer := dht.routes[level].NearestPeer(kb.ConvertKey(u.Key(pmes.GetKey()))) + + // If this peer is closer than the one from the table, return nil + if kb.Closer(dht.self.ID, closer.ID, u.Key(pmes.GetKey())) { + resp.Peers = nil + } else { + resp.Peers = []*peer.Peer{closer} + } } } else { - //temp: what other errors can a datastore throw? + //temp: what other errors can a datastore return? panic(err) } mes := swarm.NewMessage(p, resp.ToProtobuf()) - dht.network.Chan.Outgoing <- mes + dht.network.Send(mes) } // Store a value in this peer local storage @@ -290,84 +299,66 @@ func (dht *IpfsDHT) handlePing(p *peer.Peer, pmes *PBDHTMessage) { Id: pmes.GetId(), } - dht.network.Chan.Outgoing <- swarm.NewMessage(p, resp.ToProtobuf()) + dht.network.Send(swarm.NewMessage(p, resp.ToProtobuf())) } func (dht *IpfsDHT) handleFindPeer(p *peer.Peer, pmes *PBDHTMessage) { - success := true - u.POut("handleFindPeer: searching for '%s'", peer.ID(pmes.GetKey()).Pretty()) - closest := dht.routes[0].NearestPeer(kb.ConvertKey(u.Key(pmes.GetKey()))) + resp := DHTMessage{ + Type: pmes.GetType(), + Id: pmes.GetId(), + Response: true, + } + defer func() { + mes := swarm.NewMessage(p, resp.ToProtobuf()) + dht.network.Send(mes) + }() + level := pmes.GetValue()[0] + u.DOut("handleFindPeer: searching for '%s'", peer.ID(pmes.GetKey()).Pretty()) + closest := dht.routes[level].NearestPeer(kb.ConvertKey(u.Key(pmes.GetKey()))) if closest == nil { u.PErr("handleFindPeer: could not find anything.") - success = false + return } if len(closest.Addresses) == 0 { u.PErr("handleFindPeer: no addresses for connected peer...") - success = false + return } - u.POut("handleFindPeer: sending back '%s'", closest.ID.Pretty()) - - addr, err := closest.Addresses[0].String() - if err != nil { - u.PErr(err.Error()) - success = false + // If the found peer further away than this peer... + if kb.Closer(dht.self.ID, closest.ID, u.Key(pmes.GetKey())) { + return } - resp := DHTMessage{ - Type: pmes.GetType(), - Response: true, - Id: pmes.GetId(), - Value: []byte(addr), - Success: success, - } - - mes := swarm.NewMessage(p, resp.ToProtobuf()) - dht.network.Chan.Outgoing <- mes + u.DOut("handleFindPeer: sending back '%s'", closest.ID.Pretty()) + resp.Peers = []*peer.Peer{closest} + resp.Success = true } func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *PBDHTMessage) { + resp := DHTMessage{ + Type: PBDHTMessage_GET_PROVIDERS, + Key: pmes.GetKey(), + Id: pmes.GetId(), + Response: true, + } + dht.providerLock.RLock() providers := dht.providers[u.Key(pmes.GetKey())] dht.providerLock.RUnlock() if providers == nil || len(providers) == 0 { - // ????? - u.DOut("No known providers for requested key.") - } - - // This is just a quick hack, formalize method of sending addrs later - addrs := make(map[u.Key]string) - for _, prov := range providers { - ma := prov.Value.NetAddress("tcp") - str, err := ma.String() - if err != nil { - u.PErr("Error: %s", err) - continue + // TODO: work on tiering this + closer := dht.routes[0].NearestPeer(kb.ConvertKey(u.Key(pmes.GetKey()))) + resp.Peers = []*peer.Peer{closer} + } else { + for _, prov := range providers { + resp.Peers = append(resp.Peers, prov.Value) } - - addrs[prov.Value.Key()] = str - } - - success := true - data, err := json.Marshal(addrs) - if err != nil { - u.POut("handleGetProviders: error marshalling struct to JSON: %s", err) - data = nil - success = false - } - - resp := DHTMessage{ - Type: PBDHTMessage_GET_PROVIDERS, - Key: pmes.GetKey(), - Value: data, - Id: pmes.GetId(), - Response: true, - Success: success, + resp.Success = true } mes := swarm.NewMessage(p, resp.ToProtobuf()) - dht.network.Chan.Outgoing <- mes + dht.network.Send(mes) } type providerInfo struct { @@ -445,7 +436,7 @@ func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *PBDHTMessage) { for _, ps := range seq { mes := swarm.NewMessage(ps, pmes) - dht.network.Chan.Outgoing <- mes + dht.network.Send(mes) } buf := new(bytes.Buffer) @@ -481,19 +472,21 @@ out: } mes := swarm.NewMessage(p, resp.ToProtobuf()) - dht.network.Chan.Outgoing <- mes + dht.network.Send(mes) } -func (dht *IpfsDHT) getValueSingle(p *peer.Peer, key u.Key, timeout time.Duration) ([]byte, error) { +// getValueSingle simply performs the get value RPC with the given parameters +func (dht *IpfsDHT) getValueSingle(p *peer.Peer, key u.Key, timeout time.Duration, level int) (*PBDHTMessage, error) { pmes := DHTMessage{ - Type: PBDHTMessage_GET_VALUE, - Key: string(key), - Id: GenerateMessageID(), + Type: PBDHTMessage_GET_VALUE, + Key: string(key), + Value: []byte{byte(level)}, + Id: GenerateMessageID(), } response_chan := dht.ListenFor(pmes.Id, 1, time.Minute) mes := swarm.NewMessage(p, pmes.ToProtobuf()) - dht.network.Chan.Outgoing <- mes + dht.network.Send(mes) // Wait for either the response or a timeout timeup := time.After(timeout) @@ -511,46 +504,41 @@ func (dht *IpfsDHT) getValueSingle(p *peer.Peer, key u.Key, timeout time.Duratio if err != nil { return nil, err } - // TODO: debate moving this logic out of this function to be handled by the caller - if pmes_out.GetSuccess() { - if pmes_out.Value == nil { - // We were given provider[s] - return dht.getFromProviderList(key, timeout, pmes_out.GetPeers()) - } - // We were given the value - return pmes_out.GetValue(), nil - } else { - return pmes_out.GetValue(), u.ErrSearchIncomplete - } + return pmes_out, nil } } -// TODO: Im not certain on this implementation, we get a list of providers from someone -// what do we do with it? Connect to each of them? randomly pick one to get the value from? -// Or just connect to one at a time until we get a successful connection and request the -// value from it? -func (dht *IpfsDHT) getFromProviderList(key u.Key, timeout time.Duration, provlist []*PBDHTMessage_PBPeer) ([]byte, error) { - for _, prov := range provlist { - prov_p, _ := dht.Find(peer.ID(prov.GetId())) - if prov_p == nil { - maddr, err := ma.NewMultiaddr(prov.GetAddr()) +// TODO: Im not certain on this implementation, we get a list of peers/providers +// from someone what do we do with it? Connect to each of them? randomly pick +// one to get the value from? Or just connect to one at a time until we get a +// successful connection and request the value from it? +func (dht *IpfsDHT) getFromPeerList(key u.Key, timeout time.Duration, + peerlist []*PBDHTMessage_PBPeer, level int) ([]byte, error) { + for _, pinfo := range peerlist { + p, _ := dht.Find(peer.ID(pinfo.GetId())) + if p == nil { + maddr, err := ma.NewMultiaddr(pinfo.GetAddr()) if err != nil { u.PErr("getValue error: %s", err) continue } - prov_p, err = dht.Connect(maddr) + p, err = dht.Connect(maddr) if err != nil { u.PErr("getValue error: %s", err) continue } } - data, err := dht.getValueSingle(prov_p, key, timeout) + pmes, err := dht.getValueSingle(p, key, timeout, level) if err != nil { - u.DErr("getFromProvs error: %s", err) + u.DErr("getFromPeers error: %s", err) continue } + dht.addProviderEntry(key, p) - return data, nil + // Make sure it was a successful get + if pmes.GetSuccess() && pmes.Value != nil { + return pmes.GetValue(), nil + } } return nil, u.ErrNotFound } @@ -584,3 +572,30 @@ func (dht *IpfsDHT) Find(id peer.ID) (*peer.Peer, *kb.RoutingTable) { } return nil, nil } + +func (dht *IpfsDHT) findPeerSingle(p *peer.Peer, id peer.ID, timeout time.Duration, level int) (*PBDHTMessage, error) { + pmes := DHTMessage{ + Type: PBDHTMessage_FIND_NODE, + Key: string(id), + Id: GenerateMessageID(), + Value: []byte{byte(level)}, + } + + mes := swarm.NewMessage(p, pmes.ToProtobuf()) + listenChan := dht.ListenFor(pmes.Id, 1, time.Minute) + dht.network.Send(mes) + after := time.After(timeout) + select { + case <-after: + dht.Unlisten(pmes.Id) + return nil, u.ErrTimeout + case resp := <-listenChan: + pmes_out := new(PBDHTMessage) + err := proto.Unmarshal(resp.Data, pmes_out) + if err != nil { + return nil, err + } + + return pmes_out, nil + } +} diff --git a/routing/dht/dht_test.go b/routing/dht/dht_test.go index 177128978..3d1679397 100644 --- a/routing/dht/dht_test.go +++ b/routing/dht/dht_test.go @@ -11,6 +11,37 @@ import ( "time" ) +func setupDHTS(n int, t *testing.T) ([]*ma.Multiaddr, []*peer.Peer, []*IpfsDHT) { + var addrs []*ma.Multiaddr + for i := 0; i < 4; i++ { + a, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 5000+i)) + if err != nil { + t.Fatal(err) + } + addrs = append(addrs, a) + } + + var peers []*peer.Peer + for i := 0; i < 4; i++ { + p := new(peer.Peer) + p.AddAddress(addrs[i]) + p.ID = peer.ID([]byte(fmt.Sprintf("peer_%d", i))) + peers = append(peers, p) + } + + var dhts []*IpfsDHT + for i := 0; i < 4; i++ { + d, err := NewDHT(peers[i]) + if err != nil { + t.Fatal(err) + } + dhts = append(dhts, d) + d.Start() + } + + return addrs, peers, dhts +} + func TestPing(t *testing.T) { u.Debug = false addr_a, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/2222") @@ -90,11 +121,13 @@ func TestValueGetSet(t *testing.T) { dht_a.Start() dht_b.Start() + errsa := dht_a.network.GetChan().Errors + errsb := dht_b.network.GetChan().Errors go func() { select { - case err := <-dht_a.network.Chan.Errors: + case err := <-errsa: t.Fatal(err) - case err := <-dht_b.network.Chan.Errors: + case err := <-errsb: t.Fatal(err) } }() @@ -118,32 +151,8 @@ func TestValueGetSet(t *testing.T) { func TestProvides(t *testing.T) { u.Debug = false - var addrs []*ma.Multiaddr - for i := 0; i < 4; i++ { - a, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 5000+i)) - if err != nil { - t.Fatal(err) - } - addrs = append(addrs, a) - } - var peers []*peer.Peer - for i := 0; i < 4; i++ { - p := new(peer.Peer) - p.AddAddress(addrs[i]) - p.ID = peer.ID([]byte(fmt.Sprintf("peer_%d", i))) - peers = append(peers, p) - } - - var dhts []*IpfsDHT - for i := 0; i < 4; i++ { - d, err := NewDHT(peers[i]) - if err != nil { - t.Fatal(err) - } - dhts = append(dhts, d) - d.Start() - } + addrs, _, dhts := setupDHTS(4, t) _, err := dhts[0].Connect(addrs[1]) if err != nil { @@ -217,7 +226,7 @@ func TestLayeredGet(t *testing.T) { _, err := dhts[0].Connect(addrs[1]) if err != nil { - t.Fatal(err) + t.Fatalf("Failed to connect: %s", err) } _, err = dhts[1].Connect(addrs[2]) @@ -255,3 +264,65 @@ func TestLayeredGet(t *testing.T) { dhts[i].Halt() } } + +func TestFindPeer(t *testing.T) { + u.Debug = false + var addrs []*ma.Multiaddr + for i := 0; i < 4; i++ { + a, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 5000+i)) + if err != nil { + t.Fatal(err) + } + addrs = append(addrs, a) + } + + var peers []*peer.Peer + for i := 0; i < 4; i++ { + p := new(peer.Peer) + p.AddAddress(addrs[i]) + p.ID = peer.ID([]byte(fmt.Sprintf("peer_%d", i))) + peers = append(peers, p) + } + + var dhts []*IpfsDHT + for i := 0; i < 4; i++ { + d, err := NewDHT(peers[i]) + if err != nil { + t.Fatal(err) + } + dhts = append(dhts, d) + d.Start() + } + + _, err := dhts[0].Connect(addrs[1]) + if err != nil { + t.Fatal(err) + } + + _, err = dhts[1].Connect(addrs[2]) + if err != nil { + t.Fatal(err) + } + + _, err = dhts[1].Connect(addrs[3]) + if err != nil { + t.Fatal(err) + } + + p, err := dhts[0].FindPeer(peers[2].ID, time.Second) + if err != nil { + t.Fatal(err) + } + + if p == nil { + t.Fatal("Failed to find peer.") + } + + if !p.ID.Equal(peers[2].ID) { + t.Fatal("Didnt find expected peer.") + } + + for i := 0; i < 4; i++ { + dhts[i].Halt() + } +} diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 2c6a9b74c..711866f8e 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -3,8 +3,6 @@ package dht import ( "bytes" "encoding/json" - "errors" - "fmt" "math/rand" "time" @@ -35,19 +33,19 @@ func GenerateMessageID() uint64 { // This is the top level "Store" operation of the DHT func (s *IpfsDHT) PutValue(key u.Key, value []byte) { complete := make(chan struct{}) - for i, route := range s.routes { + for _, route := range s.routes { p := route.NearestPeer(kb.ConvertKey(key)) if p == nil { - s.network.Chan.Errors <- fmt.Errorf("No peer found on level %d", i) - continue + s.network.Error(kb.ErrLookupFailure) go func() { complete <- struct{}{} }() + continue } go func() { err := s.putValueToNetwork(p, string(key), value) if err != nil { - s.network.Chan.Errors <- err + s.network.Error(err) } complete <- struct{}{} }() @@ -61,19 +59,46 @@ func (s *IpfsDHT) PutValue(key u.Key, value []byte) { // If the search does not succeed, a multiaddr string of a closer peer is // returned along with util.ErrSearchIncomplete func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { - for _, route := range s.routes { - var p *peer.Peer - p = route.NearestPeer(kb.ConvertKey(key)) - if p == nil { - return nil, errors.New("Table returned nil peer!") + route_level := 0 + + p := s.routes[route_level].NearestPeer(kb.ConvertKey(key)) + if p == nil { + return nil, kb.ErrLookupFailure + } + + for route_level < len(s.routes) && p != nil { + pmes, err := s.getValueSingle(p, key, timeout, route_level) + if err != nil { + return nil, u.WrapError(err, "getValue Error") } - b, err := s.getValueSingle(p, key, timeout) - if err == nil { - return b, nil - } - if err != u.ErrSearchIncomplete { - return nil, err + if pmes.GetSuccess() { + if pmes.Value == nil { // We were given provider[s] + return s.getFromPeerList(key, timeout, pmes.GetPeers(), route_level) + } + + // Success! We were given the value + return pmes.GetValue(), nil + } else { + // We were given a closer node + closers := pmes.GetPeers() + if len(closers) > 0 { + maddr, err := ma.NewMultiaddr(closers[0].GetAddr()) + if err != nil { + // ??? Move up route level??? + panic("not yet implemented") + } + + // TODO: dht.Connect has overhead due to an internal + // ping to the target. Use something else + p, err = s.Connect(maddr) + if err != nil { + // Move up route level + panic("not yet implemented.") + } + } else { + route_level++ + } } } return nil, u.ErrNotFound @@ -86,7 +111,7 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { func (s *IpfsDHT) Provide(key u.Key) error { peers := s.routes[0].NearestPeers(kb.ConvertKey(key), PoolSize) if len(peers) == 0 { - //return an error + return kb.ErrLookupFailure } pmes := DHTMessage{ @@ -97,7 +122,7 @@ func (s *IpfsDHT) Provide(key u.Key) error { for _, p := range peers { mes := swarm.NewMessage(p, pbmes) - s.network.Chan.Outgoing <- mes + s.network.Send(mes) } return nil } @@ -105,6 +130,9 @@ func (s *IpfsDHT) Provide(key u.Key) error { // FindProviders searches for peers who can provide the value for given key. func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, error) { p := s.routes[0].NearestPeer(kb.ConvertKey(key)) + if p == nil { + return nil, kb.ErrLookupFailure + } pmes := DHTMessage{ Type: PBDHTMessage_GET_PROVIDERS, @@ -116,7 +144,7 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, listenChan := s.ListenFor(pmes.Id, 1, time.Minute) u.DOut("Find providers for: '%s'", key) - s.network.Chan.Outgoing <- mes + s.network.Send(mes) after := time.After(timeout) select { case <-after: @@ -129,17 +157,12 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, if err != nil { return nil, err } - var addrs map[u.Key]string - err = json.Unmarshal(pmes_out.GetValue(), &addrs) - if err != nil { - return nil, err - } var prov_arr []*peer.Peer - for pid, addr := range addrs { - p := s.network.Find(pid) + for _, prov := range pmes_out.GetPeers() { + p := s.network.Find(u.Key(prov.GetId())) if p == nil { - maddr, err := ma.NewMultiaddr(addr) + maddr, err := ma.NewMultiaddr(prov.GetAddr()) if err != nil { u.PErr("error connecting to new peer: %s", err) continue @@ -162,48 +185,36 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, // FindPeer searches for a peer with given ID. func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error) { - p := s.routes[0].NearestPeer(kb.ConvertPeerID(id)) - - pmes := DHTMessage{ - Type: PBDHTMessage_FIND_NODE, - Key: string(id), - Id: GenerateMessageID(), + route_level := 0 + p := s.routes[route_level].NearestPeer(kb.ConvertPeerID(id)) + if p == nil { + return nil, kb.ErrLookupFailure } - mes := swarm.NewMessage(p, pmes.ToProtobuf()) + for route_level < len(s.routes) { + pmes, err := s.findPeerSingle(p, id, timeout, route_level) + plist := pmes.GetPeers() + if len(plist) == 0 { + route_level++ + } + found := plist[0] - listenChan := s.ListenFor(pmes.Id, 1, time.Minute) - s.network.Chan.Outgoing <- mes - after := time.After(timeout) - select { - case <-after: - s.Unlisten(pmes.Id) - return nil, u.ErrTimeout - case resp := <-listenChan: - pmes_out := new(PBDHTMessage) - err := proto.Unmarshal(resp.Data, pmes_out) + addr, err := ma.NewMultiaddr(found.GetAddr()) if err != nil { - return nil, err + return nil, u.WrapError(err, "FindPeer received bad info") } - addr := string(pmes_out.GetValue()) - maddr, err := ma.NewMultiaddr(addr) + + nxtPeer, err := s.Connect(addr) if err != nil { - return nil, err + return nil, u.WrapError(err, "FindPeer failed to connect to new peer.") } - - found_peer, err := s.Connect(maddr) - if err != nil { - u.POut("Found peer but couldnt connect.") - return nil, err + if pmes.GetSuccess() { + return nxtPeer, nil + } else { + p = nxtPeer } - - if !found_peer.ID.Equal(id) { - u.POut("FindPeer: searching for '%s' but found '%s'", id.Pretty(), found_peer.ID.Pretty()) - return found_peer, u.ErrSearchIncomplete - } - - return found_peer, nil } + return nil, u.ErrNotFound } // Ping a peer, log the time it took @@ -216,14 +227,14 @@ func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { before := time.Now() response_chan := dht.ListenFor(pmes.Id, 1, time.Minute) - dht.network.Chan.Outgoing <- mes + dht.network.Send(mes) tout := time.After(timeout) select { case <-response_chan: roundtrip := time.Since(before) p.SetLatency(roundtrip) - u.POut("Ping took %s.", roundtrip.String()) + u.DOut("Ping took %s.", roundtrip.String()) return nil case <-tout: // Timed out, think about removing peer from network @@ -249,7 +260,7 @@ func (dht *IpfsDHT) GetDiagnostic(timeout time.Duration) ([]*diagInfo, error) { pbmes := pmes.ToProtobuf() for _, p := range targets { mes := swarm.NewMessage(p, pbmes) - dht.network.Chan.Outgoing <- mes + dht.network.Send(mes) } var out []*diagInfo diff --git a/routing/kbucket/util.go b/routing/kbucket/util.go index 095d0b03e..32ff2c269 100644 --- a/routing/kbucket/util.go +++ b/routing/kbucket/util.go @@ -3,11 +3,16 @@ package dht import ( "bytes" "crypto/sha256" + "errors" peer "github.com/jbenet/go-ipfs/peer" u "github.com/jbenet/go-ipfs/util" ) +// Returned if a routing table query returns no results. This is NOT expected +// behaviour +var ErrLookupFailure = errors.New("failed to find any peer in table") + // ID for IpfsDHT should be a byte slice, to allow for simpler operations // (xor). DHT ids are based on the peer.IDs. // @@ -19,8 +24,8 @@ func (id ID) Equal(other ID) bool { return bytes.Equal(id, other) } -func (id ID) Less(other interface{}) bool { - a, b := equalizeSizes(id, other.(ID)) +func (id ID) Less(other ID) bool { + a, b := equalizeSizes(id, other) for i := 0; i < len(a); i++ { if a[i] != b[i] { return a[i] < b[i] @@ -80,3 +85,14 @@ func ConvertKey(id u.Key) ID { hash := sha256.Sum256([]byte(id)) return hash[:] } + +// Returns true if a is closer to key than b is +func Closer(a, b peer.ID, key u.Key) bool { + aid := ConvertPeerID(a) + bid := ConvertPeerID(b) + tgt := ConvertKey(key) + adist := xor(aid, tgt) + bdist := xor(bid, tgt) + + return adist.Less(bdist) +} diff --git a/swarm/swarm.go b/swarm/swarm.go index 93ad68755..5a75ea723 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -355,3 +355,17 @@ func (s *Swarm) Drop(p *peer.Peer) error { return conn.Close() } + +func (s *Swarm) Send(mes *Message) { + s.Chan.Outgoing <- mes +} + +func (s *Swarm) Error(e error) { + s.Chan.Errors <- e +} + +func (s *Swarm) GetChan() *Chan { + return s.Chan +} + +var _ Network = &Swarm{} diff --git a/util/util.go b/util/util.go index ac9ca8100..8ffd43f11 100644 --- a/util/util.go +++ b/util/util.go @@ -1,10 +1,12 @@ package util import ( + "bytes" "errors" "fmt" "os" "os/user" + "runtime" "strings" b58 "github.com/jbenet/go-base58" @@ -34,6 +36,30 @@ func (k Key) Pretty() string { return b58.Encode([]byte(k)) } +type IpfsError struct { + Inner error + Note string + Stack string +} + +func (ie *IpfsError) Error() string { + buf := new(bytes.Buffer) + fmt.Fprintln(buf, ie.Inner) + fmt.Fprintln(buf, ie.Note) + fmt.Fprintln(buf, ie.Stack) + return buf.String() +} + +func WrapError(err error, note string) error { + ie := new(IpfsError) + ie.Inner = err + ie.Note = note + stack := make([]byte, 2048) + n := runtime.Stack(stack, false) + ie.Stack = string(stack[:n]) + return ie +} + // Hash is the global IPFS hash function. uses multihash SHA2_256, 256 bits func Hash(data []byte) (mh.Multihash, error) { return mh.Sum(data, mh.SHA2_256, -1) From 0a41abdd1d270a4c1eed14cc782675b971beb858 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Sun, 10 Aug 2014 21:40:17 -0700 Subject: [PATCH 23/30] starting a new testing framework --- routing/dht/dht.go | 15 ++------ routing/dht/dht_test.go | 74 ++++++++++----------------------------- routing/dht/ext_test.go | 77 +++++++++++++++++++++++++++++++++++++++++ swarm/interface.go | 19 ++++++++++ 4 files changed, 117 insertions(+), 68 deletions(-) create mode 100644 routing/dht/ext_test.go create mode 100644 swarm/interface.go diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 388ab9ab1..e62b1fda8 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -66,18 +66,9 @@ type listenInfo struct { } // NewDHT creates a new DHT object with the given peer as the 'local' host -func NewDHT(p *peer.Peer) (*IpfsDHT, error) { - if p == nil { - return nil, errors.New("nil peer passed to NewDHT()") - } - network := swarm.NewSwarm(p) - err := network.Listen() - if err != nil { - return nil, err - } - +func NewDHT(p *peer.Peer, net swarm.Network) *IpfsDHT { dht := new(IpfsDHT) - dht.network = network + dht.network = net dht.datastore = ds.NewMapDatastore() dht.self = p dht.listeners = make(map[uint64]*listenInfo) @@ -86,7 +77,7 @@ func NewDHT(p *peer.Peer) (*IpfsDHT, error) { dht.routes = make([]*kb.RoutingTable, 1) dht.routes[0] = kb.NewRoutingTable(20, kb.ConvertPeerID(p.ID)) dht.birth = time.Now() - return dht, nil + return dht } // Start up background goroutines needed by the DHT diff --git a/routing/dht/dht_test.go b/routing/dht/dht_test.go index 3d1679397..9c198eb6b 100644 --- a/routing/dht/dht_test.go +++ b/routing/dht/dht_test.go @@ -4,6 +4,7 @@ import ( "testing" peer "github.com/jbenet/go-ipfs/peer" + swarm "github.com/jbenet/go-ipfs/swarm" u "github.com/jbenet/go-ipfs/util" ma "github.com/jbenet/go-multiaddr" @@ -31,10 +32,12 @@ func setupDHTS(n int, t *testing.T) ([]*ma.Multiaddr, []*peer.Peer, []*IpfsDHT) var dhts []*IpfsDHT for i := 0; i < 4; i++ { - d, err := NewDHT(peers[i]) + net := swarm.NewSwarm(peers[i]) + err := net.Listen() if err != nil { t.Fatal(err) } + d := NewDHT(peers[i], net) dhts = append(dhts, d) d.Start() } @@ -61,15 +64,19 @@ func TestPing(t *testing.T) { peer_b.AddAddress(addr_b) peer_b.ID = peer.ID([]byte("peer_b")) - dht_a, err := NewDHT(peer_a) + neta := swarm.NewSwarm(peer_a) + err = neta.Listen() if err != nil { t.Fatal(err) } + dht_a := NewDHT(peer_a, neta) - dht_b, err := NewDHT(peer_b) + netb := swarm.NewSwarm(peer_b) + err = netb.Listen() if err != nil { t.Fatal(err) } + dht_b := NewDHT(peer_b, netb) dht_a.Start() dht_b.Start() @@ -108,15 +115,19 @@ func TestValueGetSet(t *testing.T) { peer_b.AddAddress(addr_b) peer_b.ID = peer.ID([]byte("peer_b")) - dht_a, err := NewDHT(peer_a) + neta := swarm.NewSwarm(peer_a) + err = neta.Listen() if err != nil { t.Fatal(err) } + dht_a := NewDHT(peer_a, neta) - dht_b, err := NewDHT(peer_b) + netb := swarm.NewSwarm(peer_b) + err = netb.Listen() if err != nil { t.Fatal(err) } + dht_b := NewDHT(peer_b, netb) dht_a.Start() dht_b.Start() @@ -197,32 +208,7 @@ func TestProvides(t *testing.T) { func TestLayeredGet(t *testing.T) { u.Debug = false - var addrs []*ma.Multiaddr - for i := 0; i < 4; i++ { - a, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 5000+i)) - if err != nil { - t.Fatal(err) - } - addrs = append(addrs, a) - } - - var peers []*peer.Peer - for i := 0; i < 4; i++ { - p := new(peer.Peer) - p.AddAddress(addrs[i]) - p.ID = peer.ID([]byte(fmt.Sprintf("peer_%d", i))) - peers = append(peers, p) - } - - var dhts []*IpfsDHT - for i := 0; i < 4; i++ { - d, err := NewDHT(peers[i]) - if err != nil { - t.Fatal(err) - } - dhts = append(dhts, d) - d.Start() - } + addrs,_,dhts := setupDHTS(4, t) _, err := dhts[0].Connect(addrs[1]) if err != nil { @@ -267,32 +253,8 @@ func TestLayeredGet(t *testing.T) { func TestFindPeer(t *testing.T) { u.Debug = false - var addrs []*ma.Multiaddr - for i := 0; i < 4; i++ { - a, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 5000+i)) - if err != nil { - t.Fatal(err) - } - addrs = append(addrs, a) - } - var peers []*peer.Peer - for i := 0; i < 4; i++ { - p := new(peer.Peer) - p.AddAddress(addrs[i]) - p.ID = peer.ID([]byte(fmt.Sprintf("peer_%d", i))) - peers = append(peers, p) - } - - var dhts []*IpfsDHT - for i := 0; i < 4; i++ { - d, err := NewDHT(peers[i]) - if err != nil { - t.Fatal(err) - } - dhts = append(dhts, d) - d.Start() - } + addrs,peers,dhts := setupDHTS(4, t) _, err := dhts[0].Connect(addrs[1]) if err != nil { diff --git a/routing/dht/ext_test.go b/routing/dht/ext_test.go new file mode 100644 index 000000000..dd781d867 --- /dev/null +++ b/routing/dht/ext_test.go @@ -0,0 +1,77 @@ +package dht + +import ( + "testing" + + peer "github.com/jbenet/go-ipfs/peer" + u "github.com/jbenet/go-ipfs/util" + swarm "github.com/jbenet/go-ipfs/swarm" + //ma "github.com/jbenet/go-multiaddr" + + "fmt" + "time" +) + +// fauxNet is a standin for a swarm.Network in order to more easily recreate +// different testing scenarios +type fauxNet struct { + Chan *swarm.Chan + + swarm.Network + + handlers []mesHandleFunc +} + +type mesHandleFunc func(*swarm.Message) *swarm.Message + +func newFauxNet() *fauxNet { + fn := new(fauxNet) + fn.Chan = swarm.NewChan(8) + + return fn +} + +func (f *fauxNet) Listen() error { + go func() { + for { + select { + case in := <-f.Chan.Outgoing: + for _,h := range f.handlers { + reply := h(in) + if reply != nil { + f.Chan.Incoming <- reply + break + } + } + } + } + }() + return nil +} + +func (f *fauxNet) AddHandler(fn func(*swarm.Message) *swarm.Message) { + f.handlers = append(f.handlers, fn) +} + +func (f *fauxNet) Send(mes *swarm.Message) { + +} + +func TestGetFailure(t *testing.T) { + fn := newFauxNet() + fn.Listen() + + local := new(peer.Peer) + local.ID = peer.ID([]byte("test_peer")) + + d := NewDHT(local, fn) + + d.Start() + + b, err := d.GetValue(u.Key("test"), time.Second) + if err != nil { + t.Fatal(err) + } + + fmt.Println(b) +} diff --git a/swarm/interface.go b/swarm/interface.go new file mode 100644 index 000000000..88248837c --- /dev/null +++ b/swarm/interface.go @@ -0,0 +1,19 @@ +package swarm + +import ( + peer "github.com/jbenet/go-ipfs/peer" + u "github.com/jbenet/go-ipfs/util" + + ma "github.com/jbenet/go-multiaddr" +) + +type Network interface { + Send(*Message) + Error(error) + Find(u.Key) *peer.Peer + Listen() error + Connect(*ma.Multiaddr) (*peer.Peer, error) + GetChan() *Chan + Close() + Drop(*peer.Peer) error +} From 4cb2e1e07be7ca308d624f459f96774e00493519 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Mon, 11 Aug 2014 09:06:20 -0700 Subject: [PATCH 24/30] add fauxNet to stand in for Swarm in tests to reproduce various network conditions --- routing/dht/dht_test.go | 4 +-- routing/dht/ext_test.go | 70 ++++++++++++++++++++++++++++++++++------- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/routing/dht/dht_test.go b/routing/dht/dht_test.go index 9c198eb6b..2b0fb4a6b 100644 --- a/routing/dht/dht_test.go +++ b/routing/dht/dht_test.go @@ -208,7 +208,7 @@ func TestProvides(t *testing.T) { func TestLayeredGet(t *testing.T) { u.Debug = false - addrs,_,dhts := setupDHTS(4, t) + addrs, _, dhts := setupDHTS(4, t) _, err := dhts[0].Connect(addrs[1]) if err != nil { @@ -254,7 +254,7 @@ func TestLayeredGet(t *testing.T) { func TestFindPeer(t *testing.T) { u.Debug = false - addrs,peers,dhts := setupDHTS(4, t) + addrs, peers, dhts := setupDHTS(4, t) _, err := dhts[0].Connect(addrs[1]) if err != nil { diff --git a/routing/dht/ext_test.go b/routing/dht/ext_test.go index dd781d867..4dff36886 100644 --- a/routing/dht/ext_test.go +++ b/routing/dht/ext_test.go @@ -3,12 +3,13 @@ package dht import ( "testing" - peer "github.com/jbenet/go-ipfs/peer" - u "github.com/jbenet/go-ipfs/util" - swarm "github.com/jbenet/go-ipfs/swarm" - //ma "github.com/jbenet/go-multiaddr" + "code.google.com/p/goprotobuf/proto" + + peer "github.com/jbenet/go-ipfs/peer" + swarm "github.com/jbenet/go-ipfs/swarm" + u "github.com/jbenet/go-ipfs/util" + ma "github.com/jbenet/go-multiaddr" - "fmt" "time" ) @@ -36,7 +37,7 @@ func (f *fauxNet) Listen() error { for { select { case in := <-f.Chan.Outgoing: - for _,h := range f.handlers { + for _, h := range f.handlers { reply := h(in) if reply != nil { f.Chan.Incoming <- reply @@ -54,24 +55,69 @@ func (f *fauxNet) AddHandler(fn func(*swarm.Message) *swarm.Message) { } func (f *fauxNet) Send(mes *swarm.Message) { - + f.Chan.Outgoing <- mes } -func TestGetFailure(t *testing.T) { +func (f *fauxNet) GetChan() *swarm.Chan { + return f.Chan +} + +func (f *fauxNet) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { + return nil, nil +} + +func TestGetFailures(t *testing.T) { fn := newFauxNet() fn.Listen() local := new(peer.Peer) - local.ID = peer.ID([]byte("test_peer")) + local.ID = peer.ID("test_peer") d := NewDHT(local, fn) + other := &peer.Peer{ID: peer.ID("other_peer")} + d.Start() - b, err := d.GetValue(u.Key("test"), time.Second) + d.Update(other) + + // This one should time out + _, err := d.GetValue(u.Key("test"), time.Millisecond*5) if err != nil { - t.Fatal(err) + nerr, ok := err.(*u.IpfsError) + if !ok { + t.Fatal("Got different error than we expected.") + } + if nerr.Inner != u.ErrTimeout { + t.Fatal("Got different error than we expected.") + } + } else { + t.Fatal("Did not get expected error!") } - fmt.Println(b) + fn.AddHandler(func(mes *swarm.Message) *swarm.Message { + pmes := new(PBDHTMessage) + err := proto.Unmarshal(mes.Data, pmes) + if err != nil { + t.Fatal(err) + } + + resp := DHTMessage{ + Type: pmes.GetType(), + Id: pmes.GetId(), + Response: true, + Success: false, + } + return swarm.NewMessage(mes.Peer, resp.ToProtobuf()) + }) + + // This one should fail with NotFound + _, err = d.GetValue(u.Key("test"), time.Millisecond*5) + if err != nil { + if err != u.ErrNotFound { + t.Fatal("Expected ErrNotFound, got: %s", err) + } + } else { + t.Fatal("expected error, got none.") + } } From f09dba772cdcc5cb7705a816db8d83917a65d50e Mon Sep 17 00:00:00 2001 From: Jeromy Date: Mon, 11 Aug 2014 20:11:23 -0700 Subject: [PATCH 25/30] more tests and add in table filtering by peer latency --- peer/peer.go | 2 + routing/dht/dht.go | 49 +++++++++++++++++--- routing/dht/ext_test.go | 39 ++++++++++++++-- routing/dht/routing.go | 8 ++-- routing/kbucket/bucket.go | 54 ++++++++++++++-------- routing/kbucket/table.go | 30 +++++++++--- routing/kbucket/table_test.go | 87 ++++++++++++++++++++++++++++++++--- 7 files changed, 223 insertions(+), 46 deletions(-) diff --git a/peer/peer.go b/peer/peer.go index a54179b3b..144189f58 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -71,6 +71,8 @@ func (p *Peer) GetLatency() (out time.Duration) { return } +// TODO: Instead of just keeping a single number, +// keep a running average over the last hour or so func (p *Peer) SetLatency(laten time.Duration) { p.latenLock.Lock() p.latency = laten diff --git a/routing/dht/dht.go b/routing/dht/dht.go index e62b1fda8..018c22a01 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -74,8 +74,12 @@ func NewDHT(p *peer.Peer, net swarm.Network) *IpfsDHT { dht.listeners = make(map[uint64]*listenInfo) dht.providers = make(map[u.Key][]*providerInfo) dht.shutdown = make(chan struct{}) - dht.routes = make([]*kb.RoutingTable, 1) - dht.routes[0] = kb.NewRoutingTable(20, kb.ConvertPeerID(p.ID)) + + dht.routes = make([]*kb.RoutingTable, 3) + dht.routes[0] = kb.NewRoutingTable(20, kb.ConvertPeerID(p.ID), time.Millisecond*30) + dht.routes[1] = kb.NewRoutingTable(20, kb.ConvertPeerID(p.ID), time.Millisecond*100) + dht.routes[2] = kb.NewRoutingTable(20, kb.ConvertPeerID(p.ID), time.Hour) + dht.birth = time.Now() return dht } @@ -253,7 +257,13 @@ func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *PBDHTMessage) { // No providers? // Find closest peer on given cluster to desired key and reply with that info - level := pmes.GetValue()[0] // Using value field to specify cluster level + level := 0 + if len(pmes.GetValue()) < 1 { + // TODO: maybe return an error? Defaulting isnt a good idea IMO + u.PErr("handleGetValue: no routing level specified, assuming 0") + } else { + level = int(pmes.GetValue()[0]) // Using value field to specify cluster level + } closer := dht.routes[level].NearestPeer(kb.ConvertKey(u.Key(pmes.GetKey()))) @@ -477,6 +487,7 @@ func (dht *IpfsDHT) getValueSingle(p *peer.Peer, key u.Key, timeout time.Duratio response_chan := dht.ListenFor(pmes.Id, 1, time.Minute) mes := swarm.NewMessage(p, pmes.ToProtobuf()) + t := time.Now() dht.network.Send(mes) // Wait for either the response or a timeout @@ -490,6 +501,8 @@ func (dht *IpfsDHT) getValueSingle(p *peer.Peer, key u.Key, timeout time.Duratio u.PErr("response channel closed before timeout, please investigate.") return nil, u.ErrTimeout } + roundtrip := time.Since(t) + resp.Peer.SetLatency(roundtrip) pmes_out := new(PBDHTMessage) err := proto.Unmarshal(resp.Data, pmes_out) if err != nil { @@ -513,7 +526,8 @@ func (dht *IpfsDHT) getFromPeerList(key u.Key, timeout time.Duration, u.PErr("getValue error: %s", err) continue } - p, err = dht.Connect(maddr) + + p, err = dht.network.Connect(maddr) if err != nil { u.PErr("getValue error: %s", err) continue @@ -547,9 +561,21 @@ func (dht *IpfsDHT) PutLocal(key u.Key, value []byte) error { } func (dht *IpfsDHT) Update(p *peer.Peer) { - removed := dht.routes[0].Update(p) - if removed != nil { - dht.network.Drop(removed) + for _, route := range dht.routes { + removed := route.Update(p) + // Only drop the connection if no tables refer to this peer + if removed != nil { + found := false + for _, r := range dht.routes { + if r.Find(removed.ID) != nil { + found = true + break + } + } + if !found { + dht.network.Drop(removed) + } + } } } @@ -574,6 +600,7 @@ func (dht *IpfsDHT) findPeerSingle(p *peer.Peer, id peer.ID, timeout time.Durati mes := swarm.NewMessage(p, pmes.ToProtobuf()) listenChan := dht.ListenFor(pmes.Id, 1, time.Minute) + t := time.Now() dht.network.Send(mes) after := time.After(timeout) select { @@ -581,6 +608,8 @@ func (dht *IpfsDHT) findPeerSingle(p *peer.Peer, id peer.ID, timeout time.Durati dht.Unlisten(pmes.Id) return nil, u.ErrTimeout case resp := <-listenChan: + roundtrip := time.Since(t) + resp.Peer.SetLatency(roundtrip) pmes_out := new(PBDHTMessage) err := proto.Unmarshal(resp.Data, pmes_out) if err != nil { @@ -590,3 +619,9 @@ func (dht *IpfsDHT) findPeerSingle(p *peer.Peer, id peer.ID, timeout time.Durati return pmes_out, nil } } + +func (dht *IpfsDHT) PrintTables() { + for _, route := range dht.routes { + route.Print() + } +} diff --git a/routing/dht/ext_test.go b/routing/dht/ext_test.go index 4dff36886..fbf52a263 100644 --- a/routing/dht/ext_test.go +++ b/routing/dht/ext_test.go @@ -16,13 +16,16 @@ import ( // fauxNet is a standin for a swarm.Network in order to more easily recreate // different testing scenarios type fauxNet struct { - Chan *swarm.Chan + Chan *swarm.Chan + handlers []mesHandleFunc swarm.Network - - handlers []mesHandleFunc } +// mesHandleFunc is a function that takes in outgoing messages +// and can respond to them, simulating other peers on the network. +// returning nil will chose not to respond and pass the message onto the +// next registered handler type mesHandleFunc func(*swarm.Message) *swarm.Message func newFauxNet() *fauxNet { @@ -32,6 +35,9 @@ func newFauxNet() *fauxNet { return fn } +// Instead of 'Listening' Start up a goroutine that will check +// all outgoing messages against registered message handlers, +// and reply if needed func (f *fauxNet) Listen() error { go func() { for { @@ -95,6 +101,7 @@ func TestGetFailures(t *testing.T) { t.Fatal("Did not get expected error!") } + // Reply with failures to every message fn.AddHandler(func(mes *swarm.Message) *swarm.Message { pmes := new(PBDHTMessage) err := proto.Unmarshal(mes.Data, pmes) @@ -120,4 +127,30 @@ func TestGetFailures(t *testing.T) { } else { t.Fatal("expected error, got none.") } + + success := make(chan struct{}) + fn.handlers = nil + fn.AddHandler(func(mes *swarm.Message) *swarm.Message { + resp := new(PBDHTMessage) + err := proto.Unmarshal(mes.Data, resp) + if err != nil { + t.Fatal(err) + } + if resp.GetSuccess() { + t.Fatal("Get returned success when it shouldnt have.") + } + success <- struct{}{} + return nil + }) + + // Now we test this DHT's handleGetValue failure + req := DHTMessage{ + Type: PBDHTMessage_GET_VALUE, + Key: "hello", + Id: GenerateMessageID(), + Value: []byte{0}, + } + fn.Chan.Incoming <- swarm.NewMessage(other, req.ToProtobuf()) + + <-success } diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 711866f8e..71e950102 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -89,9 +89,7 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { panic("not yet implemented") } - // TODO: dht.Connect has overhead due to an internal - // ping to the target. Use something else - p, err = s.Connect(maddr) + p, err = s.network.Connect(maddr) if err != nil { // Move up route level panic("not yet implemented.") @@ -167,7 +165,7 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, u.PErr("error connecting to new peer: %s", err) continue } - p, err = s.Connect(maddr) + p, err = s.network.Connect(maddr) if err != nil { u.PErr("error connecting to new peer: %s", err) continue @@ -204,7 +202,7 @@ func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error return nil, u.WrapError(err, "FindPeer received bad info") } - nxtPeer, err := s.Connect(addr) + nxtPeer, err := s.network.Connect(addr) if err != nil { return nil, u.WrapError(err, "FindPeer failed to connect to new peer.") } diff --git a/routing/kbucket/bucket.go b/routing/kbucket/bucket.go index 5abd2c910..1a55a0f69 100644 --- a/routing/kbucket/bucket.go +++ b/routing/kbucket/bucket.go @@ -2,16 +2,27 @@ package dht import ( "container/list" + "sync" peer "github.com/jbenet/go-ipfs/peer" ) // Bucket holds a list of peers. -type Bucket list.List +type Bucket struct { + lk sync.RWMutex + list *list.List +} + +func NewBucket() *Bucket { + b := new(Bucket) + b.list = list.New() + return b +} func (b *Bucket) Find(id peer.ID) *list.Element { - bucket_list := (*list.List)(b) - for e := bucket_list.Front(); e != nil; e = e.Next() { + b.lk.RLock() + defer b.lk.RUnlock() + for e := b.list.Front(); e != nil; e = e.Next() { if e.Value.(*peer.Peer).ID.Equal(id) { return e } @@ -20,34 +31,42 @@ func (b *Bucket) Find(id peer.ID) *list.Element { } func (b *Bucket) MoveToFront(e *list.Element) { - bucket_list := (*list.List)(b) - bucket_list.MoveToFront(e) + b.lk.Lock() + b.list.MoveToFront(e) + b.lk.Unlock() } func (b *Bucket) PushFront(p *peer.Peer) { - bucket_list := (*list.List)(b) - bucket_list.PushFront(p) + b.lk.Lock() + b.list.PushFront(p) + b.lk.Unlock() } func (b *Bucket) PopBack() *peer.Peer { - bucket_list := (*list.List)(b) - last := bucket_list.Back() - bucket_list.Remove(last) + b.lk.Lock() + defer b.lk.Unlock() + last := b.list.Back() + b.list.Remove(last) return last.Value.(*peer.Peer) } func (b *Bucket) Len() int { - bucket_list := (*list.List)(b) - return bucket_list.Len() + b.lk.RLock() + defer b.lk.RUnlock() + return b.list.Len() } // Splits a buckets peers into two buckets, the methods receiver will have // peers with CPL equal to cpl, the returned bucket will have peers with CPL // greater than cpl (returned bucket has closer peers) func (b *Bucket) Split(cpl int, target ID) *Bucket { - bucket_list := (*list.List)(b) + b.lk.Lock() + defer b.lk.Unlock() + out := list.New() - e := bucket_list.Front() + newbuck := NewBucket() + newbuck.list = out + e := b.list.Front() for e != nil { peer_id := ConvertPeerID(e.Value.(*peer.Peer).ID) peer_cpl := prefLen(peer_id, target) @@ -55,15 +74,14 @@ func (b *Bucket) Split(cpl int, target ID) *Bucket { cur := e out.PushBack(e.Value) e = e.Next() - bucket_list.Remove(cur) + b.list.Remove(cur) continue } e = e.Next() } - return (*Bucket)(out) + return newbuck } func (b *Bucket) getIter() *list.Element { - bucket_list := (*list.List)(b) - return bucket_list.Front() + return b.list.Front() } diff --git a/routing/kbucket/table.go b/routing/kbucket/table.go index 788d12265..86a7031ce 100644 --- a/routing/kbucket/table.go +++ b/routing/kbucket/table.go @@ -2,8 +2,10 @@ package dht import ( "container/list" + "fmt" "sort" "sync" + "time" peer "github.com/jbenet/go-ipfs/peer" u "github.com/jbenet/go-ipfs/util" @@ -18,16 +20,20 @@ type RoutingTable struct { // Blanket lock, refine later for better performance tabLock sync.RWMutex + // Maximum acceptable latency for peers in this cluster + maxLatency time.Duration + // kBuckets define all the fingers to other nodes. Buckets []*Bucket bucketsize int } -func NewRoutingTable(bucketsize int, local_id ID) *RoutingTable { +func NewRoutingTable(bucketsize int, local_id ID, latency time.Duration) *RoutingTable { rt := new(RoutingTable) - rt.Buckets = []*Bucket{new(Bucket)} + rt.Buckets = []*Bucket{NewBucket()} rt.bucketsize = bucketsize rt.local = local_id + rt.maxLatency = latency return rt } @@ -48,6 +54,10 @@ func (rt *RoutingTable) Update(p *peer.Peer) *peer.Peer { e := bucket.Find(p.ID) if e == nil { // New peer, add to bucket + if p.GetLatency() > rt.maxLatency { + // Connection doesnt meet requirements, skip! + return nil + } bucket.PushFront(p) // Are we past the max bucket size? @@ -150,17 +160,16 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { // In the case of an unusual split, one bucket may be empty. // if this happens, search both surrounding buckets for nearest peer if cpl > 0 { - plist := (*list.List)(rt.Buckets[cpl-1]) + plist := rt.Buckets[cpl-1].list peerArr = copyPeersFromList(id, peerArr, plist) } if cpl < len(rt.Buckets)-1 { - plist := (*list.List)(rt.Buckets[cpl+1]) + plist := rt.Buckets[cpl+1].list peerArr = copyPeersFromList(id, peerArr, plist) } } else { - plist := (*list.List)(bucket) - peerArr = copyPeersFromList(id, peerArr, plist) + peerArr = copyPeersFromList(id, peerArr, bucket.list) } // Sort by distance to local peer @@ -193,3 +202,12 @@ func (rt *RoutingTable) Listpeers() []*peer.Peer { } return peers } + +func (rt *RoutingTable) Print() { + fmt.Printf("Routing Table, bs = %d, Max latency = %d\n", rt.bucketsize, rt.maxLatency) + rt.tabLock.RLock() + peers := rt.Listpeers() + for i, p := range peers { + fmt.Printf("%d) %s %s\n", i, p.ID.Pretty(), p.GetLatency().String()) + } +} diff --git a/routing/kbucket/table_test.go b/routing/kbucket/table_test.go index 842c92510..02d8f5e0e 100644 --- a/routing/kbucket/table_test.go +++ b/routing/kbucket/table_test.go @@ -1,11 +1,11 @@ package dht import ( - "container/list" crand "crypto/rand" "crypto/sha256" "math/rand" "testing" + "time" peer "github.com/jbenet/go-ipfs/peer" ) @@ -27,7 +27,7 @@ func _randID() ID { // Test basic features of the bucket struct func TestBucket(t *testing.T) { - b := new(Bucket) + b := NewBucket() peers := make([]*peer.Peer, 100) for i := 0; i < 100; i++ { @@ -45,7 +45,7 @@ func TestBucket(t *testing.T) { } spl := b.Split(0, ConvertPeerID(local.ID)) - llist := (*list.List)(b) + llist := b.list for e := llist.Front(); e != nil; e = e.Next() { p := ConvertPeerID(e.Value.(*peer.Peer).ID) cpl := xor(p, local_id).commonPrefixLen() @@ -54,7 +54,7 @@ func TestBucket(t *testing.T) { } } - rlist := (*list.List)(spl) + rlist := spl.list for e := rlist.Front(); e != nil; e = e.Next() { p := ConvertPeerID(e.Value.(*peer.Peer).ID) cpl := xor(p, local_id).commonPrefixLen() @@ -67,7 +67,7 @@ func TestBucket(t *testing.T) { // Right now, this just makes sure that it doesnt hang or crash func TestTableUpdate(t *testing.T) { local := _randPeer() - rt := NewRoutingTable(10, ConvertPeerID(local.ID)) + rt := NewRoutingTable(10, ConvertPeerID(local.ID), time.Hour) peers := make([]*peer.Peer, 100) for i := 0; i < 100; i++ { @@ -93,7 +93,7 @@ func TestTableUpdate(t *testing.T) { func TestTableFind(t *testing.T) { local := _randPeer() - rt := NewRoutingTable(10, ConvertPeerID(local.ID)) + rt := NewRoutingTable(10, ConvertPeerID(local.ID), time.Hour) peers := make([]*peer.Peer, 100) for i := 0; i < 5; i++ { @@ -110,7 +110,7 @@ func TestTableFind(t *testing.T) { func TestTableFindMultiple(t *testing.T) { local := _randPeer() - rt := NewRoutingTable(20, ConvertPeerID(local.ID)) + rt := NewRoutingTable(20, ConvertPeerID(local.ID), time.Hour) peers := make([]*peer.Peer, 100) for i := 0; i < 18; i++ { @@ -124,3 +124,76 @@ func TestTableFindMultiple(t *testing.T) { t.Fatalf("Got back different number of peers than we expected.") } } + +// Looks for race conditions in table operations. For a more 'certain' +// test, increase the loop counter from 1000 to a much higher number +// and set GOMAXPROCS above 1 +func TestTableMultithreaded(t *testing.T) { + local := peer.ID("localPeer") + tab := NewRoutingTable(20, ConvertPeerID(local), time.Hour) + var peers []*peer.Peer + for i := 0; i < 500; i++ { + peers = append(peers, _randPeer()) + } + + done := make(chan struct{}) + go func() { + for i := 0; i < 1000; i++ { + n := rand.Intn(len(peers)) + tab.Update(peers[n]) + } + done <- struct{}{} + }() + + go func() { + for i := 0; i < 1000; i++ { + n := rand.Intn(len(peers)) + tab.Update(peers[n]) + } + done <- struct{}{} + }() + + go func() { + for i := 0; i < 1000; i++ { + n := rand.Intn(len(peers)) + tab.Find(peers[n].ID) + } + done <- struct{}{} + }() + <-done + <-done + <-done +} + +func BenchmarkUpdates(b *testing.B) { + b.StopTimer() + local := ConvertKey("localKey") + tab := NewRoutingTable(20, local, time.Hour) + + var peers []*peer.Peer + for i := 0; i < b.N; i++ { + peers = append(peers, _randPeer()) + } + + b.StartTimer() + for i := 0; i < b.N; i++ { + tab.Update(peers[i]) + } +} + +func BenchmarkFinds(b *testing.B) { + b.StopTimer() + local := ConvertKey("localKey") + tab := NewRoutingTable(20, local, time.Hour) + + var peers []*peer.Peer + for i := 0; i < b.N; i++ { + peers = append(peers, _randPeer()) + tab.Update(peers[i]) + } + + b.StartTimer() + for i := 0; i < b.N; i++ { + tab.Find(peers[i].ID) + } +} From b8a6fbbf7d46c8ef9b26dafea712ac0cc0462541 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Tue, 12 Aug 2014 15:37:26 -0700 Subject: [PATCH 26/30] modify use of swarm to not make duplicate connections --- identify/identify.go | 2 +- routing/dht/DHTMessage.go | 2 ++ routing/dht/dht.go | 4 +-- routing/dht/routing.go | 26 ++++++++++---- swarm/conn.go | 3 +- swarm/interface.go | 3 +- swarm/swarm.go | 73 +++++++++++++++++++++++++++------------ swarm/swarm_test.go | 8 +++-- 8 files changed, 84 insertions(+), 37 deletions(-) diff --git a/identify/identify.go b/identify/identify.go index b6c67f2c5..20ad21c9a 100644 --- a/identify/identify.go +++ b/identify/identify.go @@ -14,7 +14,7 @@ func Handshake(self, remote *peer.Peer, in, out chan []byte) error { out <- self.ID resp := <-in remote.ID = peer.ID(resp) - u.DOut("identify: Got node id: %s", remote.ID.Pretty()) + u.DOut("[%s] identify: Got node id: %s", self.ID.Pretty(), remote.ID.Pretty()) return nil } diff --git a/routing/dht/DHTMessage.go b/routing/dht/DHTMessage.go index 701f36687..e2034d7e0 100644 --- a/routing/dht/DHTMessage.go +++ b/routing/dht/DHTMessage.go @@ -28,6 +28,8 @@ func peerInfo(p *peer.Peer) *PBDHTMessage_PBPeer { return pbp } +// TODO: building the protobuf message this way is a little wasteful +// Unused fields wont be omitted, find a better way to do this func (m *DHTMessage) ToProtobuf() *PBDHTMessage { pmes := new(PBDHTMessage) if m.Value != nil { diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 018c22a01..c9b32f90b 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -93,7 +93,7 @@ func (dht *IpfsDHT) Start() { func (dht *IpfsDHT) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { maddrstr, _ := addr.String() u.DOut("Connect to new peer: %s", maddrstr) - npeer, err := dht.network.Connect(addr) + npeer, err := dht.network.ConnectNew(addr) if err != nil { return nil, err } @@ -527,7 +527,7 @@ func (dht *IpfsDHT) getFromPeerList(key u.Key, timeout time.Duration, continue } - p, err = dht.network.Connect(maddr) + p, err = dht.network.GetConnection(peer.ID(pinfo.GetId()), maddr) if err != nil { u.PErr("getValue error: %s", err) continue diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 71e950102..045b17f41 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -3,6 +3,7 @@ package dht import ( "bytes" "encoding/json" + "errors" "math/rand" "time" @@ -89,10 +90,10 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { panic("not yet implemented") } - p, err = s.network.Connect(maddr) + p, err = s.network.GetConnection(peer.ID(closers[0].GetId()), maddr) if err != nil { - // Move up route level - panic("not yet implemented.") + u.PErr("[%s] Failed to connect to: %s", s.self.ID.Pretty(), closers[0].GetAddr()) + route_level++ } } else { route_level++ @@ -160,12 +161,13 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, for _, prov := range pmes_out.GetPeers() { p := s.network.Find(u.Key(prov.GetId())) if p == nil { + u.DOut("given provider %s was not in our network already.", peer.ID(prov.GetId()).Pretty()) maddr, err := ma.NewMultiaddr(prov.GetAddr()) if err != nil { u.PErr("error connecting to new peer: %s", err) continue } - p, err = s.network.Connect(maddr) + p, err = s.network.GetConnection(peer.ID(prov.GetId()), maddr) if err != nil { u.PErr("error connecting to new peer: %s", err) continue @@ -183,11 +185,20 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, // FindPeer searches for a peer with given ID. func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error) { + // Check if were already connected to them + p, _ := s.Find(id) + if p != nil { + return p, nil + } + route_level := 0 - p := s.routes[route_level].NearestPeer(kb.ConvertPeerID(id)) + p = s.routes[route_level].NearestPeer(kb.ConvertPeerID(id)) if p == nil { return nil, kb.ErrLookupFailure } + if p.ID.Equal(id) { + return p, nil + } for route_level < len(s.routes) { pmes, err := s.findPeerSingle(p, id, timeout, route_level) @@ -202,11 +213,14 @@ func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error return nil, u.WrapError(err, "FindPeer received bad info") } - nxtPeer, err := s.network.Connect(addr) + nxtPeer, err := s.network.GetConnection(peer.ID(found.GetId()), addr) if err != nil { return nil, u.WrapError(err, "FindPeer failed to connect to new peer.") } if pmes.GetSuccess() { + if !id.Equal(nxtPeer.ID) { + return nil, errors.New("got back invalid peer from 'successful' response") + } return nxtPeer, nil } else { p = nxtPeer diff --git a/swarm/conn.go b/swarm/conn.go index 56e8eea17..072b53437 100644 --- a/swarm/conn.go +++ b/swarm/conn.go @@ -2,11 +2,12 @@ package swarm import ( "fmt" + "net" + peer "github.com/jbenet/go-ipfs/peer" u "github.com/jbenet/go-ipfs/util" msgio "github.com/jbenet/go-msgio" ma "github.com/jbenet/go-multiaddr" - "net" ) // ChanBuffer is the size of the buffer in the Conn Chan diff --git a/swarm/interface.go b/swarm/interface.go index 88248837c..9a70890e6 100644 --- a/swarm/interface.go +++ b/swarm/interface.go @@ -12,7 +12,8 @@ type Network interface { Error(error) Find(u.Key) *peer.Peer Listen() error - Connect(*ma.Multiaddr) (*peer.Peer, error) + ConnectNew(*ma.Multiaddr) (*peer.Peer, error) + GetConnection(id peer.ID, addr *ma.Multiaddr) (*peer.Peer, error) GetChan() *Chan Close() Drop(*peer.Peer) error diff --git a/swarm/swarm.go b/swarm/swarm.go index 5a75ea723..12e82a6ec 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -203,7 +203,7 @@ func (s *Swarm) Close() { // etc. to achive connection. // // For now, Dial uses only TCP. This will be extended. -func (s *Swarm) Dial(peer *peer.Peer) (*Conn, error) { +func (s *Swarm) Dial(peer *peer.Peer) (*Conn, error, bool) { k := peer.Key() // check if we already have an open connection first @@ -211,17 +211,16 @@ func (s *Swarm) Dial(peer *peer.Peer) (*Conn, error) { conn, found := s.conns[k] s.connsLock.RUnlock() if found { - return conn, nil + return conn, nil, true } // open connection to peer conn, err := Dial("tcp", peer) if err != nil { - return nil, err + return nil, err, false } - s.StartConn(conn) - return conn, nil + return conn, nil, false } func (s *Swarm) StartConn(conn *Conn) error { @@ -309,7 +308,50 @@ func (s *Swarm) Find(key u.Key) *peer.Peer { return conn.Peer } -func (s *Swarm) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { +// GetConnection will check if we are already connected to the peer in question +// and only open a new connection if we arent already +func (s *Swarm) GetConnection(id peer.ID, addr *ma.Multiaddr) (*peer.Peer, error) { + p := &peer.Peer{ + ID: id, + Addresses: []*ma.Multiaddr{addr}, + } + + conn, err, reused := s.Dial(p) + if err != nil { + return nil, err + } + + if reused { + return p, nil + } + + err = s.handleDialedCon(conn) + return conn.Peer, err +} + +func (s *Swarm) handleDialedCon(conn *Conn) error { + err := ident.Handshake(s.local, conn.Peer, conn.Incoming.MsgChan, conn.Outgoing.MsgChan) + if err != nil { + return err + } + + // Send node an address that you can be reached on + myaddr := s.local.NetAddress("tcp") + mastr, err := myaddr.String() + if err != nil { + errors.New("No local address to send to peer.") + } + + conn.Outgoing.MsgChan <- []byte(mastr) + + s.StartConn(conn) + + return nil +} + +// ConnectNew is for connecting to a peer when you dont know their ID, +// Should only be used when you are sure that you arent already connected to peer in question +func (s *Swarm) ConnectNew(addr *ma.Multiaddr) (*peer.Peer, error) { if addr == nil { return nil, errors.New("nil Multiaddr passed to swarm.Connect()") } @@ -321,23 +363,8 @@ func (s *Swarm) Connect(addr *ma.Multiaddr) (*peer.Peer, error) { return nil, err } - err = ident.Handshake(s.local, npeer, conn.Incoming.MsgChan, conn.Outgoing.MsgChan) - if err != nil { - return nil, err - } - - // Send node an address that you can be reached on - myaddr := s.local.NetAddress("tcp") - mastr, err := myaddr.String() - if err != nil { - return nil, errors.New("No local address to send to peer.") - } - - conn.Outgoing.MsgChan <- []byte(mastr) - - s.StartConn(conn) - - return npeer, nil + err = s.handleDialedCon(conn) + return npeer, err } // Removes a given peer from the swarm and closes connections to it diff --git a/swarm/swarm_test.go b/swarm/swarm_test.go index b23919ecc..609288c38 100644 --- a/swarm/swarm_test.go +++ b/swarm/swarm_test.go @@ -2,11 +2,12 @@ package swarm import ( "fmt" + "net" + "testing" + peer "github.com/jbenet/go-ipfs/peer" u "github.com/jbenet/go-ipfs/util" msgio "github.com/jbenet/go-msgio" - "net" - "testing" ) func pingListen(listener *net.TCPListener, peer *peer.Peer) { @@ -71,11 +72,12 @@ func TestSwarm(t *testing.T) { } go pingListen(listener.(*net.TCPListener), peer) - _, err = swarm.Dial(peer) + conn, err, _ := swarm.Dial(peer) if err != nil { t.Fatal("error swarm dialing to peer", err) } + swarm.StartConn(conn) // ok done, add it. peers = append(peers, peer) listeners = append(listeners, listener) From 8542380e8d65f54e056edbda3863df7d6f04e2ff Mon Sep 17 00:00:00 2001 From: Jeromy Date: Tue, 12 Aug 2014 22:10:44 -0700 Subject: [PATCH 27/30] not quite working yet, but closer --- routing/dht/dht.go | 5 +++-- routing/dht/routing.go | 20 +++++++++++++++++++- swarm/swarm.go | 4 ++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/routing/dht/dht.go b/routing/dht/dht.go index c9b32f90b..04935ea82 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -158,8 +158,8 @@ func (dht *IpfsDHT) handleMessages() { } // - u.DOut("[peer: %s]", dht.self.ID.Pretty()) - u.DOut("Got message type: '%s' [id = %x, from = %s]", + u.DOut("[peer: %s]\nGot message type: '%s' [id = %x, from = %s]", + dht.self.ID.Pretty(), PBDHTMessage_MessageType_name[int32(pmes.GetType())], pmes.GetId(), mes.Peer.ID.Pretty()) switch pmes.GetType() { @@ -235,6 +235,7 @@ func (dht *IpfsDHT) putValueToNetwork(p *peer.Peer, key string, value []byte) er } func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *PBDHTMessage) { + u.DOut("handleGetValue for key: %s", pmes.GetKey()) dskey := ds.NewKey(pmes.GetKey()) resp := &DHTMessage{ Response: true, diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 045b17f41..6dc4aa060 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -62,11 +62,22 @@ func (s *IpfsDHT) PutValue(key u.Key, value []byte) { func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { route_level := 0 + // If we have it local, dont bother doing an RPC! + // NOTE: this might not be what we want to do... + val,err := s.GetLocal(key) + if err != nil { + return val, nil + } + p := s.routes[route_level].NearestPeer(kb.ConvertKey(key)) if p == nil { return nil, kb.ErrLookupFailure } + if kb.Closer(s.self.ID, p.ID, key) { + return nil, u.ErrNotFound + } + for route_level < len(s.routes) && p != nil { pmes, err := s.getValueSingle(p, key, timeout, route_level) if err != nil { @@ -84,17 +95,21 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { // We were given a closer node closers := pmes.GetPeers() if len(closers) > 0 { + if peer.ID(closers[0].GetId()).Equal(s.self.ID) { + return nil, u.ErrNotFound + } maddr, err := ma.NewMultiaddr(closers[0].GetAddr()) if err != nil { // ??? Move up route level??? panic("not yet implemented") } - p, err = s.network.GetConnection(peer.ID(closers[0].GetId()), maddr) + np, err := s.network.GetConnection(peer.ID(closers[0].GetId()), maddr) if err != nil { u.PErr("[%s] Failed to connect to: %s", s.self.ID.Pretty(), closers[0].GetAddr()) route_level++ } + p = np } else { route_level++ } @@ -159,6 +174,9 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, var prov_arr []*peer.Peer for _, prov := range pmes_out.GetPeers() { + if peer.ID(prov.GetId()).Equal(s.self.ID) { + continue + } p := s.network.Find(u.Key(prov.GetId())) if p == nil { u.DOut("given provider %s was not in our network already.", peer.ID(prov.GetId()).Pretty()) diff --git a/swarm/swarm.go b/swarm/swarm.go index 12e82a6ec..cf968984c 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -316,6 +316,10 @@ func (s *Swarm) GetConnection(id peer.ID, addr *ma.Multiaddr) (*peer.Peer, error Addresses: []*ma.Multiaddr{addr}, } + if id.Equal(s.local.ID) { + panic("Attempted connection to self!") + } + conn, err, reused := s.Dial(p) if err != nil { return nil, err From 60d061cb4942b630ab4b12bc74b03f6366df4b78 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Thu, 14 Aug 2014 08:32:17 -0700 Subject: [PATCH 28/30] fix a few infinitely looping RPCs --- routing/dht/dht.go | 82 ++++++++++++++++++++++++++++++++-- routing/dht/dht_logger.go | 38 ++++++++++++++++ routing/dht/dht_test.go | 2 +- routing/dht/routing.go | 94 +++++++++++++++++++-------------------- 4 files changed, 165 insertions(+), 51 deletions(-) create mode 100644 routing/dht/dht_logger.go diff --git a/routing/dht/dht.go b/routing/dht/dht.go index 04935ea82..ea8e1d861 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -244,12 +244,14 @@ func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *PBDHTMessage) { } iVal, err := dht.datastore.Get(dskey) if err == nil { + u.DOut("handleGetValue success!") resp.Success = true resp.Value = iVal.([]byte) } else if err == ds.ErrNotFound { // Check if we know any providers for the requested value provs, ok := dht.providers[u.Key(pmes.GetKey())] if ok && len(provs) > 0 { + u.DOut("handleGetValue returning %d provider[s]", len(provs)) for _, prov := range provs { resp.Peers = append(resp.Peers, prov.Value) } @@ -265,13 +267,21 @@ func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *PBDHTMessage) { } else { level = int(pmes.GetValue()[0]) // Using value field to specify cluster level } + u.DOut("handleGetValue searching level %d clusters", level) closer := dht.routes[level].NearestPeer(kb.ConvertKey(u.Key(pmes.GetKey()))) + if closer.ID.Equal(dht.self.ID) { + u.DOut("Attempted to return self! this shouldnt happen...") + resp.Peers = nil + goto out + } // If this peer is closer than the one from the table, return nil if kb.Closer(dht.self.ID, closer.ID, u.Key(pmes.GetKey())) { resp.Peers = nil + u.DOut("handleGetValue could not find a closer node than myself.") } else { + u.DOut("handleGetValue returning a closer peer: '%s'", closer.ID.Pretty()) resp.Peers = []*peer.Peer{closer} } } @@ -280,6 +290,7 @@ func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *PBDHTMessage) { panic(err) } +out: mes := swarm.NewMessage(p, resp.ToProtobuf()) dht.network.Send(mes) } @@ -349,9 +360,17 @@ func (dht *IpfsDHT) handleGetProviders(p *peer.Peer, pmes *PBDHTMessage) { providers := dht.providers[u.Key(pmes.GetKey())] dht.providerLock.RUnlock() if providers == nil || len(providers) == 0 { - // TODO: work on tiering this - closer := dht.routes[0].NearestPeer(kb.ConvertKey(u.Key(pmes.GetKey()))) - resp.Peers = []*peer.Peer{closer} + level := 0 + if len(pmes.GetValue()) > 0 { + level = int(pmes.GetValue()[0]) + } + + closer := dht.routes[level].NearestPeer(kb.ConvertKey(u.Key(pmes.GetKey()))) + if kb.Closer(dht.self.ID, closer.ID, u.Key(pmes.GetKey())) { + resp.Peers = nil + } else { + resp.Peers = []*peer.Peer{closer} + } } else { for _, prov := range providers { resp.Peers = append(resp.Peers, prov.Value) @@ -626,3 +645,60 @@ func (dht *IpfsDHT) PrintTables() { route.Print() } } + +func (dht *IpfsDHT) findProvidersSingle(p *peer.Peer, key u.Key, level int, timeout time.Duration) (*PBDHTMessage, error) { + pmes := DHTMessage{ + Type: PBDHTMessage_GET_PROVIDERS, + Key: string(key), + Id: GenerateMessageID(), + Value: []byte{byte(level)}, + } + + mes := swarm.NewMessage(p, pmes.ToProtobuf()) + + listenChan := dht.ListenFor(pmes.Id, 1, time.Minute) + dht.network.Send(mes) + after := time.After(timeout) + select { + case <-after: + dht.Unlisten(pmes.Id) + return nil, u.ErrTimeout + case resp := <-listenChan: + u.DOut("FindProviders: got response.") + pmes_out := new(PBDHTMessage) + err := proto.Unmarshal(resp.Data, pmes_out) + if err != nil { + return nil, err + } + + return pmes_out, nil + } +} + +func (dht *IpfsDHT) addPeerList(key u.Key, peers []*PBDHTMessage_PBPeer) []*peer.Peer { + var prov_arr []*peer.Peer + for _, prov := range peers { + // Dont add outselves to the list + if peer.ID(prov.GetId()).Equal(dht.self.ID) { + continue + } + // Dont add someone who is already on the list + p := dht.network.Find(u.Key(prov.GetId())) + if p == nil { + u.DOut("given provider %s was not in our network already.", peer.ID(prov.GetId()).Pretty()) + maddr, err := ma.NewMultiaddr(prov.GetAddr()) + if err != nil { + u.PErr("error connecting to new peer: %s", err) + continue + } + p, err = dht.network.GetConnection(peer.ID(prov.GetId()), maddr) + if err != nil { + u.PErr("error connecting to new peer: %s", err) + continue + } + } + dht.addProviderEntry(key, p) + prov_arr = append(prov_arr, p) + } + return prov_arr +} diff --git a/routing/dht/dht_logger.go b/routing/dht/dht_logger.go new file mode 100644 index 000000000..c363add7b --- /dev/null +++ b/routing/dht/dht_logger.go @@ -0,0 +1,38 @@ +package dht + +import ( + "encoding/json" + "time" + + u "github.com/jbenet/go-ipfs/util" +) + +type logDhtRpc struct { + Type string + Start time.Time + End time.Time + Duration time.Duration + RpcCount int + Success bool +} + +func startNewRpc(name string) *logDhtRpc { + r := new(logDhtRpc) + r.Type = name + r.Start = time.Now() + return r +} + +func (l *logDhtRpc) EndLog() { + l.End = time.Now() + l.Duration = l.End.Sub(l.Start) +} + +func (l *logDhtRpc) Print() { + b, err := json.Marshal(l) + if err != nil { + u.POut(err.Error()) + } else { + u.POut(string(b)) + } +} diff --git a/routing/dht/dht_test.go b/routing/dht/dht_test.go index 2b0fb4a6b..a7e14d703 100644 --- a/routing/dht/dht_test.go +++ b/routing/dht/dht_test.go @@ -156,7 +156,7 @@ func TestValueGetSet(t *testing.T) { } if string(val) != "world" { - t.Fatalf("Expected 'world' got %s", string(val)) + t.Fatalf("Expected 'world' got '%s'", string(val)) } } diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 6dc4aa060..9923961d1 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -60,12 +60,19 @@ func (s *IpfsDHT) PutValue(key u.Key, value []byte) { // If the search does not succeed, a multiaddr string of a closer peer is // returned along with util.ErrSearchIncomplete func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { + ll := startNewRpc("GET") + defer func() { + ll.EndLog() + ll.Print() + }() route_level := 0 // If we have it local, dont bother doing an RPC! // NOTE: this might not be what we want to do... - val,err := s.GetLocal(key) - if err != nil { + val, err := s.GetLocal(key) + if err == nil { + ll.Success = true + u.DOut("Found local, returning.") return val, nil } @@ -74,11 +81,8 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { return nil, kb.ErrLookupFailure } - if kb.Closer(s.self.ID, p.ID, key) { - return nil, u.ErrNotFound - } - for route_level < len(s.routes) && p != nil { + ll.RpcCount++ pmes, err := s.getValueSingle(p, key, timeout, route_level) if err != nil { return nil, u.WrapError(err, "getValue Error") @@ -86,16 +90,19 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { if pmes.GetSuccess() { if pmes.Value == nil { // We were given provider[s] + ll.RpcCount++ return s.getFromPeerList(key, timeout, pmes.GetPeers(), route_level) } // Success! We were given the value + ll.Success = true return pmes.GetValue(), nil } else { // We were given a closer node closers := pmes.GetPeers() if len(closers) > 0 { if peer.ID(closers[0].GetId()).Equal(s.self.ID) { + u.DOut("Got myself back as a closer peer.") return nil, u.ErrNotFound } maddr, err := ma.NewMultiaddr(closers[0].GetAddr()) @@ -108,6 +115,7 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { if err != nil { u.PErr("[%s] Failed to connect to: %s", s.self.ID.Pretty(), closers[0].GetAddr()) route_level++ + continue } p = np } else { @@ -143,60 +151,52 @@ func (s *IpfsDHT) Provide(key u.Key) error { // FindProviders searches for peers who can provide the value for given key. func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer, error) { + ll := startNewRpc("FindProviders") + defer func() { + ll.EndLog() + ll.Print() + }() + u.DOut("Find providers for: '%s'", key) p := s.routes[0].NearestPeer(kb.ConvertKey(key)) if p == nil { return nil, kb.ErrLookupFailure } - pmes := DHTMessage{ - Type: PBDHTMessage_GET_PROVIDERS, - Key: string(key), - Id: GenerateMessageID(), - } - - mes := swarm.NewMessage(p, pmes.ToProtobuf()) - - listenChan := s.ListenFor(pmes.Id, 1, time.Minute) - u.DOut("Find providers for: '%s'", key) - s.network.Send(mes) - after := time.After(timeout) - select { - case <-after: - s.Unlisten(pmes.Id) - return nil, u.ErrTimeout - case resp := <-listenChan: - u.DOut("FindProviders: got response.") - pmes_out := new(PBDHTMessage) - err := proto.Unmarshal(resp.Data, pmes_out) + for level := 0; level < len(s.routes); { + pmes, err := s.findProvidersSingle(p, key, level, timeout) if err != nil { return nil, err } - - var prov_arr []*peer.Peer - for _, prov := range pmes_out.GetPeers() { - if peer.ID(prov.GetId()).Equal(s.self.ID) { + if pmes.GetSuccess() { + provs := s.addPeerList(key, pmes.GetPeers()) + ll.Success = true + return provs, nil + } else { + closer := pmes.GetPeers() + if len(closer) == 0 { + level++ continue } - p := s.network.Find(u.Key(prov.GetId())) - if p == nil { - u.DOut("given provider %s was not in our network already.", peer.ID(prov.GetId()).Pretty()) - maddr, err := ma.NewMultiaddr(prov.GetAddr()) - if err != nil { - u.PErr("error connecting to new peer: %s", err) - continue - } - p, err = s.network.GetConnection(peer.ID(prov.GetId()), maddr) - if err != nil { - u.PErr("error connecting to new peer: %s", err) - continue - } + if peer.ID(closer[0].GetId()).Equal(s.self.ID) { + u.DOut("Got myself back as a closer peer.") + return nil, u.ErrNotFound + } + maddr, err := ma.NewMultiaddr(closer[0].GetAddr()) + if err != nil { + // ??? Move up route level??? + panic("not yet implemented") } - s.addProviderEntry(key, p) - prov_arr = append(prov_arr, p) - } - return prov_arr, nil + np, err := s.network.GetConnection(peer.ID(closer[0].GetId()), maddr) + if err != nil { + u.PErr("[%s] Failed to connect to: %s", s.self.ID.Pretty(), closer[0].GetAddr()) + level++ + continue + } + p = np + } } + return nil, u.ErrNotFound } // Find specific Peer From b7a882be89d14392c2243db2b317e01b28a31737 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Fri, 15 Aug 2014 09:39:38 -0700 Subject: [PATCH 29/30] get implementation according to kademlia spec. --- routing/dht/dht.go | 39 +++++++++++ routing/dht/dht_logger.go | 4 +- routing/dht/ext_test.go | 12 ++-- routing/dht/routing.go | 132 +++++++++++++++++++++++++++----------- 4 files changed, 141 insertions(+), 46 deletions(-) diff --git a/routing/dht/dht.go b/routing/dht/dht.go index ea8e1d861..b00ae0a4d 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -496,6 +496,45 @@ out: dht.network.Send(mes) } +func (dht *IpfsDHT) getValueOrPeers(p *peer.Peer, key u.Key, timeout time.Duration, level int) ([]byte, []*peer.Peer, error) { + pmes, err := dht.getValueSingle(p, key, timeout, level) + if err != nil { + return nil, nil, u.WrapError(err, "getValue Error") + } + + if pmes.GetSuccess() { + if pmes.Value == nil { // We were given provider[s] + val, err := dht.getFromPeerList(key, timeout, pmes.GetPeers(), level) + if err != nil { + return nil, nil, err + } + return val, nil, nil + } + + // Success! We were given the value + return pmes.GetValue(), nil, nil + } else { + // We were given a closer node + var peers []*peer.Peer + for _, pb := range pmes.GetPeers() { + addr, err := ma.NewMultiaddr(pb.GetAddr()) + if err != nil { + u.PErr(err.Error()) + continue + } + + np, err := dht.network.GetConnection(peer.ID(pb.GetId()), addr) + if err != nil { + u.PErr(err.Error()) + continue + } + + peers = append(peers, np) + } + return nil, peers, nil + } +} + // getValueSingle simply performs the get value RPC with the given parameters func (dht *IpfsDHT) getValueSingle(p *peer.Peer, key u.Key, timeout time.Duration, level int) (*PBDHTMessage, error) { pmes := DHTMessage{ diff --git a/routing/dht/dht_logger.go b/routing/dht/dht_logger.go index c363add7b..c892959f0 100644 --- a/routing/dht/dht_logger.go +++ b/routing/dht/dht_logger.go @@ -31,8 +31,8 @@ func (l *logDhtRpc) EndLog() { func (l *logDhtRpc) Print() { b, err := json.Marshal(l) if err != nil { - u.POut(err.Error()) + u.DOut(err.Error()) } else { - u.POut(string(b)) + u.DOut(string(b)) } } diff --git a/routing/dht/ext_test.go b/routing/dht/ext_test.go index fbf52a263..490c9f493 100644 --- a/routing/dht/ext_test.go +++ b/routing/dht/ext_test.go @@ -88,13 +88,9 @@ func TestGetFailures(t *testing.T) { d.Update(other) // This one should time out - _, err := d.GetValue(u.Key("test"), time.Millisecond*5) + _, err := d.GetValue(u.Key("test"), time.Millisecond*10) if err != nil { - nerr, ok := err.(*u.IpfsError) - if !ok { - t.Fatal("Got different error than we expected.") - } - if nerr.Inner != u.ErrTimeout { + if err != u.ErrTimeout { t.Fatal("Got different error than we expected.") } } else { @@ -119,10 +115,10 @@ func TestGetFailures(t *testing.T) { }) // This one should fail with NotFound - _, err = d.GetValue(u.Key("test"), time.Millisecond*5) + _, err = d.GetValue(u.Key("test"), time.Millisecond*1000) if err != nil { if err != u.ErrNotFound { - t.Fatal("Expected ErrNotFound, got: %s", err) + t.Fatalf("Expected ErrNotFound, got: %s", err) } } else { t.Fatal("expected error, got none.") diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 9923961d1..2ecd8ba45 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "math/rand" + "sync" "time" proto "code.google.com/p/goprotobuf/proto" @@ -56,6 +57,30 @@ func (s *IpfsDHT) PutValue(key u.Key, value []byte) { } } +// A counter for incrementing a variable across multiple threads +type counter struct { + n int + mut sync.RWMutex +} + +func (c *counter) Increment() { + c.mut.Lock() + c.n++ + c.mut.Unlock() +} + +func (c *counter) Decrement() { + c.mut.Lock() + c.n-- + c.mut.Unlock() +} + +func (c *counter) Size() int { + c.mut.RLock() + defer c.mut.RUnlock() + return c.n +} + // GetValue searches for the value corresponding to given Key. // If the search does not succeed, a multiaddr string of a closer peer is // returned along with util.ErrSearchIncomplete @@ -65,7 +90,6 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { ll.EndLog() ll.Print() }() - route_level := 0 // If we have it local, dont bother doing an RPC! // NOTE: this might not be what we want to do... @@ -76,54 +100,90 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { return val, nil } - p := s.routes[route_level].NearestPeer(kb.ConvertKey(key)) - if p == nil { + route_level := 0 + closest := s.routes[route_level].NearestPeers(kb.ConvertKey(key), PoolSize) + if closest == nil || len(closest) == 0 { return nil, kb.ErrLookupFailure } - for route_level < len(s.routes) && p != nil { - ll.RpcCount++ - pmes, err := s.getValueSingle(p, key, timeout, route_level) - if err != nil { - return nil, u.WrapError(err, "getValue Error") - } + val_chan := make(chan []byte) + npeer_chan := make(chan *peer.Peer, 30) + proc_peer := make(chan *peer.Peer, 30) + err_chan := make(chan error) + after := time.After(timeout) - if pmes.GetSuccess() { - if pmes.Value == nil { // We were given provider[s] - ll.RpcCount++ - return s.getFromPeerList(key, timeout, pmes.GetPeers(), route_level) + for _, p := range closest { + npeer_chan <- p + } + + c := counter{} + + // This limit value is referred to as k in the kademlia paper + limit := 20 + count := 0 + go func() { + for { + select { + case p := <-npeer_chan: + count++ + if count >= limit { + break + } + c.Increment() + proc_peer <- p + default: + if c.Size() == 0 { + err_chan <- u.ErrNotFound + } } + } + }() - // Success! We were given the value - ll.Success = true - return pmes.GetValue(), nil - } else { - // We were given a closer node - closers := pmes.GetPeers() - if len(closers) > 0 { - if peer.ID(closers[0].GetId()).Equal(s.self.ID) { - u.DOut("Got myself back as a closer peer.") - return nil, u.ErrNotFound + process := func() { + for { + select { + case p, ok := <-proc_peer: + if !ok || p == nil { + c.Decrement() + return } - maddr, err := ma.NewMultiaddr(closers[0].GetAddr()) + val, peers, err := s.getValueOrPeers(p, key, timeout/4, route_level) if err != nil { - // ??? Move up route level??? - panic("not yet implemented") - } - - np, err := s.network.GetConnection(peer.ID(closers[0].GetId()), maddr) - if err != nil { - u.PErr("[%s] Failed to connect to: %s", s.self.ID.Pretty(), closers[0].GetAddr()) - route_level++ + u.DErr(err.Error()) + c.Decrement() continue } - p = np - } else { - route_level++ + if val != nil { + val_chan <- val + c.Decrement() + return + } + + for _, np := range peers { + // TODO: filter out peers that arent closer + npeer_chan <- np + } + c.Decrement() } } } - return nil, u.ErrNotFound + + concurFactor := 3 + for i := 0; i < concurFactor; i++ { + go process() + } + + select { + case val := <-val_chan: + close(npeer_chan) + return val, nil + case err := <-err_chan: + close(npeer_chan) + return nil, err + case <-after: + close(npeer_chan) + return nil, u.ErrTimeout + } } // Value provider layer of indirection. From 8a1fdbb83d648e419a8e789e9125aa2abb2a0988 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Fri, 15 Aug 2014 22:37:53 -0700 Subject: [PATCH 30/30] rewrite message response listening framework --- routing/dht/dht.go | 117 ++++++----------------------------------- routing/dht/routing.go | 51 ++++++++++++++---- util/util.go | 26 --------- 3 files changed, 57 insertions(+), 137 deletions(-) diff --git a/routing/dht/dht.go b/routing/dht/dht.go index b00ae0a4d..c28ca0a0f 100644 --- a/routing/dht/dht.go +++ b/routing/dht/dht.go @@ -39,10 +39,6 @@ type IpfsDHT struct { providers map[u.Key][]*providerInfo providerLock sync.RWMutex - // map of channels waiting for reply messages - listeners map[uint64]*listenInfo - listenLock sync.RWMutex - // Signal to shutdown dht shutdown chan struct{} @@ -51,18 +47,9 @@ type IpfsDHT struct { //lock to make diagnostics work better diaglock sync.Mutex -} -// The listen info struct holds information about a message that is being waited for -type listenInfo struct { - // Responses matching the listen ID will be sent through resp - resp chan *swarm.Message - - // count is the number of responses to listen for - count int - - // eol is the time at which this listener will expire - eol time.Time + // listener is a server to register to listen for responses to messages + listener *MesListener } // NewDHT creates a new DHT object with the given peer as the 'local' host @@ -71,7 +58,6 @@ func NewDHT(p *peer.Peer, net swarm.Network) *IpfsDHT { dht.network = net dht.datastore = ds.NewMapDatastore() dht.self = p - dht.listeners = make(map[uint64]*listenInfo) dht.providers = make(map[u.Key][]*providerInfo) dht.shutdown = make(chan struct{}) @@ -80,6 +66,7 @@ func NewDHT(p *peer.Peer, net swarm.Network) *IpfsDHT { dht.routes[1] = kb.NewRoutingTable(20, kb.ConvertPeerID(p.ID), time.Millisecond*100) dht.routes[2] = kb.NewRoutingTable(20, kb.ConvertPeerID(p.ID), time.Hour) + dht.listener = NewMesListener() dht.birth = time.Now() return dht } @@ -135,25 +122,7 @@ func (dht *IpfsDHT) handleMessages() { // Note: not sure if this is the correct place for this if pmes.GetResponse() { - dht.listenLock.RLock() - list, ok := dht.listeners[pmes.GetId()] - dht.listenLock.RUnlock() - if time.Now().After(list.eol) { - dht.Unlisten(pmes.GetId()) - ok = false - } - if list.count > 1 { - list.count-- - } - if ok { - list.resp <- mes - if list.count == 1 { - dht.Unlisten(pmes.GetId()) - } - } else { - u.DOut("Received response with nobody listening...") - } - + dht.listener.Respond(pmes.GetId(), mes) continue } // @@ -187,7 +156,6 @@ func (dht *IpfsDHT) handleMessages() { case <-checkTimeouts.C: // Time to collect some garbage! dht.cleanExpiredProviders() - dht.cleanExpiredListeners() } } } @@ -206,21 +174,6 @@ func (dht *IpfsDHT) cleanExpiredProviders() { dht.providerLock.Unlock() } -func (dht *IpfsDHT) cleanExpiredListeners() { - dht.listenLock.Lock() - var remove []uint64 - now := time.Now() - for k, v := range dht.listeners { - if now.After(v.eol) { - remove = append(remove, k) - } - } - for _, k := range remove { - delete(dht.listeners, k) - } - dht.listenLock.Unlock() -} - func (dht *IpfsDHT) putValueToNetwork(p *peer.Peer, key string, value []byte) error { pmes := DHTMessage{ Type: PBDHTMessage_PUT_VALUE, @@ -393,41 +346,6 @@ func (dht *IpfsDHT) handleAddProvider(p *peer.Peer, pmes *PBDHTMessage) { dht.addProviderEntry(key, p) } -// Register a handler for a specific message ID, used for getting replies -// to certain messages (i.e. response to a GET_VALUE message) -func (dht *IpfsDHT) ListenFor(mesid uint64, count int, timeout time.Duration) <-chan *swarm.Message { - lchan := make(chan *swarm.Message) - dht.listenLock.Lock() - dht.listeners[mesid] = &listenInfo{lchan, count, time.Now().Add(timeout)} - dht.listenLock.Unlock() - return lchan -} - -// Unregister the given message id from the listener map -func (dht *IpfsDHT) Unlisten(mesid uint64) { - dht.listenLock.Lock() - list, ok := dht.listeners[mesid] - if ok { - delete(dht.listeners, mesid) - } - dht.listenLock.Unlock() - close(list.resp) -} - -// Check whether or not the dht is currently listening for mesid -func (dht *IpfsDHT) IsListening(mesid uint64) bool { - dht.listenLock.RLock() - li, ok := dht.listeners[mesid] - dht.listenLock.RUnlock() - if time.Now().After(li.eol) { - dht.listenLock.Lock() - delete(dht.listeners, mesid) - dht.listenLock.Unlock() - return false - } - return ok -} - // Stop all communications from this peer and shut down func (dht *IpfsDHT) Halt() { dht.shutdown <- struct{}{} @@ -444,16 +362,8 @@ func (dht *IpfsDHT) addProviderEntry(key u.Key, p *peer.Peer) { // NOTE: not yet finished, low priority func (dht *IpfsDHT) handleDiagnostic(p *peer.Peer, pmes *PBDHTMessage) { - dht.diaglock.Lock() - if dht.IsListening(pmes.GetId()) { - //TODO: ehhh.......... - dht.diaglock.Unlock() - return - } - dht.diaglock.Unlock() - seq := dht.routes[0].NearestPeers(kb.ConvertPeerID(dht.self.ID), 10) - listenChan := dht.ListenFor(pmes.GetId(), len(seq), time.Second*30) + listenChan := dht.listener.Listen(pmes.GetId(), len(seq), time.Second*30) for _, ps := range seq { mes := swarm.NewMessage(ps, pmes) @@ -499,7 +409,7 @@ out: func (dht *IpfsDHT) getValueOrPeers(p *peer.Peer, key u.Key, timeout time.Duration, level int) ([]byte, []*peer.Peer, error) { pmes, err := dht.getValueSingle(p, key, timeout, level) if err != nil { - return nil, nil, u.WrapError(err, "getValue Error") + return nil, nil, err } if pmes.GetSuccess() { @@ -517,6 +427,9 @@ func (dht *IpfsDHT) getValueOrPeers(p *peer.Peer, key u.Key, timeout time.Durati // We were given a closer node var peers []*peer.Peer for _, pb := range pmes.GetPeers() { + if peer.ID(pb.GetId()).Equal(dht.self.ID) { + continue + } addr, err := ma.NewMultiaddr(pb.GetAddr()) if err != nil { u.PErr(err.Error()) @@ -543,7 +456,7 @@ func (dht *IpfsDHT) getValueSingle(p *peer.Peer, key u.Key, timeout time.Duratio Value: []byte{byte(level)}, Id: GenerateMessageID(), } - response_chan := dht.ListenFor(pmes.Id, 1, time.Minute) + response_chan := dht.listener.Listen(pmes.Id, 1, time.Minute) mes := swarm.NewMessage(p, pmes.ToProtobuf()) t := time.Now() @@ -553,7 +466,7 @@ func (dht *IpfsDHT) getValueSingle(p *peer.Peer, key u.Key, timeout time.Duratio timeup := time.After(timeout) select { case <-timeup: - dht.Unlisten(pmes.Id) + dht.listener.Unlisten(pmes.Id) return nil, u.ErrTimeout case resp, ok := <-response_chan: if !ok { @@ -658,13 +571,13 @@ func (dht *IpfsDHT) findPeerSingle(p *peer.Peer, id peer.ID, timeout time.Durati } mes := swarm.NewMessage(p, pmes.ToProtobuf()) - listenChan := dht.ListenFor(pmes.Id, 1, time.Minute) + listenChan := dht.listener.Listen(pmes.Id, 1, time.Minute) t := time.Now() dht.network.Send(mes) after := time.After(timeout) select { case <-after: - dht.Unlisten(pmes.Id) + dht.listener.Unlisten(pmes.Id) return nil, u.ErrTimeout case resp := <-listenChan: roundtrip := time.Since(t) @@ -695,12 +608,12 @@ func (dht *IpfsDHT) findProvidersSingle(p *peer.Peer, key u.Key, level int, time mes := swarm.NewMessage(p, pmes.ToProtobuf()) - listenChan := dht.ListenFor(pmes.Id, 1, time.Minute) + listenChan := dht.listener.Listen(pmes.Id, 1, time.Minute) dht.network.Send(mes) after := time.After(timeout) select { case <-after: - dht.Unlisten(pmes.Id) + dht.listener.Unlisten(pmes.Id) return nil, u.ErrTimeout case resp := <-listenChan: u.DOut("FindProviders: got response.") diff --git a/routing/dht/routing.go b/routing/dht/routing.go index 2ecd8ba45..e56a54e0c 100644 --- a/routing/dht/routing.go +++ b/routing/dht/routing.go @@ -81,6 +81,36 @@ func (c *counter) Size() int { return c.n } +type peerSet struct { + ps map[string]bool + lk sync.RWMutex +} + +func newPeerSet() *peerSet { + ps := new(peerSet) + ps.ps = make(map[string]bool) + return ps +} + +func (ps *peerSet) Add(p *peer.Peer) { + ps.lk.Lock() + ps.ps[string(p.ID)] = true + ps.lk.Unlock() +} + +func (ps *peerSet) Contains(p *peer.Peer) bool { + ps.lk.RLock() + _, ok := ps.ps[string(p.ID)] + ps.lk.RUnlock() + return ok +} + +func (ps *peerSet) Size() int { + ps.lk.RLock() + defer ps.lk.RUnlock() + return len(ps.ps) +} + // GetValue searches for the value corresponding to given Key. // If the search does not succeed, a multiaddr string of a closer peer is // returned along with util.ErrSearchIncomplete @@ -111,8 +141,10 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { proc_peer := make(chan *peer.Peer, 30) err_chan := make(chan error) after := time.After(timeout) + pset := newPeerSet() for _, p := range closest { + pset.Add(p) npeer_chan <- p } @@ -130,6 +162,7 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { break } c.Increment() + proc_peer <- p default: if c.Size() == 0 { @@ -161,7 +194,10 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { for _, np := range peers { // TODO: filter out peers that arent closer - npeer_chan <- np + if !pset.Contains(np) && pset.Size() < limit { + pset.Add(np) //This is racey... make a single function to do operation + npeer_chan <- np + } } c.Decrement() } @@ -175,13 +211,10 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) { select { case val := <-val_chan: - close(npeer_chan) return val, nil case err := <-err_chan: - close(npeer_chan) return nil, err case <-after: - close(npeer_chan) return nil, u.ErrTimeout } } @@ -288,12 +321,12 @@ func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error addr, err := ma.NewMultiaddr(found.GetAddr()) if err != nil { - return nil, u.WrapError(err, "FindPeer received bad info") + return nil, err } nxtPeer, err := s.network.GetConnection(peer.ID(found.GetId()), addr) if err != nil { - return nil, u.WrapError(err, "FindPeer failed to connect to new peer.") + return nil, err } if pmes.GetSuccess() { if !id.Equal(nxtPeer.ID) { @@ -316,7 +349,7 @@ func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { mes := swarm.NewMessage(p, pmes.ToProtobuf()) before := time.Now() - response_chan := dht.ListenFor(pmes.Id, 1, time.Minute) + response_chan := dht.listener.Listen(pmes.Id, 1, time.Minute) dht.network.Send(mes) tout := time.After(timeout) @@ -329,7 +362,7 @@ func (dht *IpfsDHT) Ping(p *peer.Peer, timeout time.Duration) error { case <-tout: // Timed out, think about removing peer from network u.DOut("Ping peer timed out.") - dht.Unlisten(pmes.Id) + dht.listener.Unlisten(pmes.Id) return u.ErrTimeout } } @@ -345,7 +378,7 @@ func (dht *IpfsDHT) GetDiagnostic(timeout time.Duration) ([]*diagInfo, error) { Id: GenerateMessageID(), } - listenChan := dht.ListenFor(pmes.Id, len(targets), time.Minute*2) + listenChan := dht.listener.Listen(pmes.Id, len(targets), time.Minute*2) pbmes := pmes.ToProtobuf() for _, p := range targets { diff --git a/util/util.go b/util/util.go index 8ffd43f11..ac9ca8100 100644 --- a/util/util.go +++ b/util/util.go @@ -1,12 +1,10 @@ package util import ( - "bytes" "errors" "fmt" "os" "os/user" - "runtime" "strings" b58 "github.com/jbenet/go-base58" @@ -36,30 +34,6 @@ func (k Key) Pretty() string { return b58.Encode([]byte(k)) } -type IpfsError struct { - Inner error - Note string - Stack string -} - -func (ie *IpfsError) Error() string { - buf := new(bytes.Buffer) - fmt.Fprintln(buf, ie.Inner) - fmt.Fprintln(buf, ie.Note) - fmt.Fprintln(buf, ie.Stack) - return buf.String() -} - -func WrapError(err error, note string) error { - ie := new(IpfsError) - ie.Inner = err - ie.Note = note - stack := make([]byte, 2048) - n := runtime.Stack(stack, false) - ie.Stack = string(stack[:n]) - return ie -} - // Hash is the global IPFS hash function. uses multihash SHA2_256, 256 bits func Hash(data []byte) (mh.Multihash, error) { return mh.Sum(data, mh.SHA2_256, -1)