Commit 21580ccd authored by Juan Batiz-Benet's avatar Juan Batiz-Benet
Browse files

swap net2 -> net

parent 08b8250c
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
inet "github.com/jbenet/go-ipfs/p2p/net" inet "github.com/jbenet/go-ipfs/p2p/net"
peer "github.com/jbenet/go-ipfs/p2p/peer" peer "github.com/jbenet/go-ipfs/p2p/peer"
protocol "github.com/jbenet/go-ipfs/p2p/protocol"
testutil "github.com/jbenet/go-ipfs/util/testutil" testutil "github.com/jbenet/go-ipfs/util/testutil"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context" context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
...@@ -46,31 +47,44 @@ func TestNetworkSetup(t *testing.T) { ...@@ -46,31 +47,44 @@ func TestNetworkSetup(t *testing.T) {
a2 := testutil.RandLocalTCPAddress() a2 := testutil.RandLocalTCPAddress()
a3 := testutil.RandLocalTCPAddress() a3 := testutil.RandLocalTCPAddress()
n1, err := mn.AddPeer(sk1, a1) h1, err := mn.AddPeer(sk1, a1)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
p1 := n1.LocalPeer() p1 := h1.ID()
n2, err := mn.AddPeer(sk2, a2) h2, err := mn.AddPeer(sk2, a2)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
p2 := n2.LocalPeer() p2 := h2.ID()
n3, err := mn.AddPeer(sk3, a3) h3, err := mn.AddPeer(sk3, a3)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
p3 := n3.LocalPeer() p3 := h3.ID()
// check peers and net // check peers and net
if mn.Host(p1) != h1 {
t.Error("host for p1.ID != h1")
}
if mn.Host(p2) != h2 {
t.Error("host for p2.ID != h2")
}
if mn.Host(p3) != h3 {
t.Error("host for p3.ID != h3")
}
n1 := h1.Network()
if mn.Net(p1) != n1 { if mn.Net(p1) != n1 {
t.Error("net for p1.ID != n1") t.Error("net for p1.ID != n1")
} }
n2 := h2.Network()
if mn.Net(p2) != n2 { if mn.Net(p2) != n2 {
t.Error("net for p2.ID != n1") t.Error("net for p2.ID != n1")
} }
n3 := h3.Network()
if mn.Net(p3) != n3 { if mn.Net(p3) != n3 {
t.Error("net for p3.ID != n1") t.Error("net for p3.ID != n1")
} }
...@@ -177,7 +191,7 @@ func TestNetworkSetup(t *testing.T) { ...@@ -177,7 +191,7 @@ func TestNetworkSetup(t *testing.T) {
} }
// connect p2->p3 // connect p2->p3
if err := n2.DialPeer(ctx, p3); err != nil { if _, err := n2.DialPeer(ctx, p3); err != nil {
t.Error(err) t.Error(err)
} }
...@@ -191,41 +205,41 @@ func TestNetworkSetup(t *testing.T) { ...@@ -191,41 +205,41 @@ func TestNetworkSetup(t *testing.T) {
// p.NetworkConns(n3) // p.NetworkConns(n3)
// can create a stream 2->3, 3->2, // can create a stream 2->3, 3->2,
if _, err := n2.NewStream(inet.ProtocolDiag, p3); err != nil { if _, err := n2.NewStream(p3); err != nil {
t.Error(err) t.Error(err)
} }
if _, err := n3.NewStream(inet.ProtocolDiag, p2); err != nil { if _, err := n3.NewStream(p2); err != nil {
t.Error(err) t.Error(err)
} }
// but not 1->2 nor 2->2 (not linked), nor 1->1 (not connected) // but not 1->2 nor 2->2 (not linked), nor 1->1 (not connected)
if _, err := n1.NewStream(inet.ProtocolDiag, p2); err == nil { if _, err := n1.NewStream(p2); err == nil {
t.Error("should not be able to connect") t.Error("should not be able to connect")
} }
if _, err := n2.NewStream(inet.ProtocolDiag, p2); err == nil { if _, err := n2.NewStream(p2); err == nil {
t.Error("should not be able to connect") t.Error("should not be able to connect")
} }
if _, err := n1.NewStream(inet.ProtocolDiag, p1); err == nil { if _, err := n1.NewStream(p1); err == nil {
t.Error("should not be able to connect") t.Error("should not be able to connect")
} }
// connect p1->p1 (should work) // connect p1->p1 (should work)
if err := n1.DialPeer(ctx, p1); err != nil { if _, err := n1.DialPeer(ctx, p1); err != nil {
t.Error("p1 should be able to dial self.", err) t.Error("p1 should be able to dial self.", err)
} }
// and a stream too // and a stream too
if _, err := n1.NewStream(inet.ProtocolDiag, p1); err != nil { if _, err := n1.NewStream(p1); err != nil {
t.Error(err) t.Error(err)
} }
// connect p1->p2 // connect p1->p2
if err := n1.DialPeer(ctx, p2); err == nil { if _, err := n1.DialPeer(ctx, p2); err == nil {
t.Error("p1 should not be able to dial p2, not connected...") t.Error("p1 should not be able to dial p2, not connected...")
} }
// connect p3->p1 // connect p3->p1
if err := n3.DialPeer(ctx, p1); err == nil { if _, err := n3.DialPeer(ctx, p1); err == nil {
t.Error("p3 should not be able to dial p1, not connected...") t.Error("p3 should not be able to dial p1, not connected...")
} }
...@@ -243,12 +257,12 @@ func TestNetworkSetup(t *testing.T) { ...@@ -243,12 +257,12 @@ func TestNetworkSetup(t *testing.T) {
// should now be able to connect // should now be able to connect
// connect p1->p2 // connect p1->p2
if err := n1.DialPeer(ctx, p2); err != nil { if _, err := n1.DialPeer(ctx, p2); err != nil {
t.Error(err) t.Error(err)
} }
// and a stream should work now too :) // and a stream should work now too :)
if _, err := n2.NewStream(inet.ProtocolDiag, p3); err != nil { if _, err := n2.NewStream(p3); err != nil {
t.Error(err) t.Error(err)
} }
...@@ -262,27 +276,25 @@ func TestStreams(t *testing.T) { ...@@ -262,27 +276,25 @@ func TestStreams(t *testing.T) {
} }
handler := func(s inet.Stream) { handler := func(s inet.Stream) {
go func() { b := make([]byte, 4)
b := make([]byte, 4) if _, err := io.ReadFull(s, b); err != nil {
if _, err := io.ReadFull(s, b); err != nil { panic(err)
panic(err) }
} if !bytes.Equal(b, []byte("beep")) {
if !bytes.Equal(b, []byte("beep")) { panic("bytes mismatch")
panic("bytes mismatch") }
} if _, err := s.Write([]byte("boop")); err != nil {
if _, err := s.Write([]byte("boop")); err != nil { panic(err)
panic(err) }
} s.Close()
s.Close()
}()
} }
nets := mn.Nets() hosts := mn.Hosts()
for _, n := range nets { for _, h := range mn.Hosts() {
n.SetHandler(inet.ProtocolDHT, handler) h.SetStreamHandler(protocol.TestingID, handler)
} }
s, err := nets[0].NewStream(inet.ProtocolDHT, nets[1].LocalPeer()) s, err := hosts[0].NewStream(protocol.TestingID, hosts[1].ID())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -352,17 +364,10 @@ func TestStreamsStress(t *testing.T) { ...@@ -352,17 +364,10 @@ func TestStreamsStress(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
protos := []inet.ProtocolID{ hosts := mn.Hosts()
inet.ProtocolDHT, for _, h := range hosts {
inet.ProtocolBitswap, ponger := makePonger(string(protocol.TestingID))
inet.ProtocolDiag, h.SetStreamHandler(protocol.TestingID, ponger)
}
nets := mn.Nets()
for _, n := range nets {
for _, p := range protos {
n.SetHandler(p, makePonger(string(p)))
}
} }
var wg sync.WaitGroup var wg sync.WaitGroup
...@@ -370,18 +375,16 @@ func TestStreamsStress(t *testing.T) { ...@@ -370,18 +375,16 @@ func TestStreamsStress(t *testing.T) {
wg.Add(1) wg.Add(1)
go func(i int) { go func(i int) {
defer wg.Done() defer wg.Done()
from := rand.Intn(len(nets)) from := rand.Intn(len(hosts))
to := rand.Intn(len(nets)) to := rand.Intn(len(hosts))
p := rand.Intn(3) s, err := hosts[from].NewStream(protocol.TestingID, hosts[to].ID())
proto := protos[p]
s, err := nets[from].NewStream(protos[p], nets[to].LocalPeer())
if err != nil { if err != nil {
log.Debugf("%d (%s) %d (%s) %d (%s)", from, nets[from], to, nets[to], p, protos[p]) log.Debugf("%d (%s) %d (%s)", from, hosts[from], to, hosts[to])
panic(err) panic(err)
} }
log.Infof("%d start pinging", i) log.Infof("%d start pinging", i)
makePinger(string(proto), rand.Intn(100))(s) makePinger("pingpong", rand.Intn(100))(s)
log.Infof("%d done pinging", i) log.Infof("%d done pinging", i)
}(i) }(i)
} }
...@@ -401,12 +404,12 @@ func TestAdding(t *testing.T) { ...@@ -401,12 +404,12 @@ func TestAdding(t *testing.T) {
} }
a := testutil.RandLocalTCPAddress() a := testutil.RandLocalTCPAddress()
n, err := mn.AddPeer(sk, a) h, err := mn.AddPeer(sk, a)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
peers = append(peers, n.LocalPeer()) peers = append(peers, h.ID())
} }
p1 := peers[0] p1 := peers[0]
...@@ -422,40 +425,38 @@ func TestAdding(t *testing.T) { ...@@ -422,40 +425,38 @@ func TestAdding(t *testing.T) {
} }
// set the new stream handler on p2 // set the new stream handler on p2
n2 := mn.Net(p2) h2 := mn.Host(p2)
if n2 == nil { if h2 == nil {
t.Fatalf("no network for %s", p2) t.Fatalf("no host for %s", p2)
} }
n2.SetHandler(inet.ProtocolBitswap, func(s inet.Stream) { h2.SetStreamHandler(protocol.TestingID, func(s inet.Stream) {
go func() { defer s.Close()
defer s.Close()
b := make([]byte, 4) b := make([]byte, 4)
if _, err := io.ReadFull(s, b); err != nil { if _, err := io.ReadFull(s, b); err != nil {
panic(err) panic(err)
} }
if string(b) != "beep" { if string(b) != "beep" {
panic("did not beep!") panic("did not beep!")
} }
if _, err := s.Write([]byte("boop")); err != nil { if _, err := s.Write([]byte("boop")); err != nil {
panic(err) panic(err)
} }
}()
}) })
// connect p1 to p2 // connect p1 to p2
if err := mn.ConnectPeers(p1, p2); err != nil { if _, err := mn.ConnectPeers(p1, p2); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// talk to p2 // talk to p2
n1 := mn.Net(p1) h1 := mn.Host(p1)
if n1 == nil { if h1 == nil {
t.Fatalf("no network for %s", p1) t.Fatalf("no network for %s", p1)
} }
s, err := n1.NewStream(inet.ProtocolBitswap, p2) s, err := h1.NewStream(protocol.TestingID, p2)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
......
package identify
import (
"sync"
ggio "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/gogoprotobuf/io"
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
inet "github.com/jbenet/go-ipfs/p2p/net"
handshake "github.com/jbenet/go-ipfs/p2p/net/handshake"
pb "github.com/jbenet/go-ipfs/p2p/net/handshake/pb"
eventlog "github.com/jbenet/go-ipfs/util/eventlog"
)
var log = eventlog.Logger("net/identify")
// ProtocolIdentify is the ProtocolID of the Identify Service.
const ProtocolIdentify inet.ProtocolID = "/ipfs/identify"
// IDService is a structure that implements ProtocolIdentify.
// It is a trivial service that gives the other peer some
// useful information about the local peer. A sort of hello.
//
// The IDService sends:
// * Our IPFS Protocol Version
// * Our IPFS Agent Version
// * Our public Listen Addresses
type IDService struct {
Network inet.Network
// connections undergoing identification
// for wait purposes
currid map[inet.Conn]chan struct{}
currmu sync.RWMutex
}
func NewIDService(n inet.Network) *IDService {
s := &IDService{
Network: n,
currid: make(map[inet.Conn]chan struct{}),
}
n.SetHandler(ProtocolIdentify, s.RequestHandler)
return s
}
func (ids *IDService) IdentifyConn(c inet.Conn) {
ids.currmu.Lock()
if wait, found := ids.currid[c]; found {
ids.currmu.Unlock()
log.Debugf("IdentifyConn called twice on: %s", c)
<-wait // already identifying it. wait for it.
return
}
ids.currid[c] = make(chan struct{})
ids.currmu.Unlock()
s, err := c.NewStreamWithProtocol(ProtocolIdentify)
if err != nil {
log.Error("network: unable to open initial stream for %s", ProtocolIdentify)
log.Event(ids.Network.CtxGroup().Context(), "IdentifyOpenFailed", c.RemotePeer())
} else {
// ok give the response to our handler.
ids.ResponseHandler(s)
}
ids.currmu.Lock()
ch, found := ids.currid[c]
delete(ids.currid, c)
ids.currmu.Unlock()
if !found {
log.Errorf("IdentifyConn failed to find channel (programmer error) for %s", c)
return
}
close(ch) // release everyone waiting.
}
func (ids *IDService) RequestHandler(s inet.Stream) {
defer s.Close()
c := s.Conn()
w := ggio.NewDelimitedWriter(s)
mes := pb.Handshake3{}
ids.populateMessage(&mes, s.Conn())
w.WriteMsg(&mes)
log.Debugf("%s sent message to %s %s", ProtocolIdentify,
c.RemotePeer(), c.RemoteMultiaddr())
}
func (ids *IDService) ResponseHandler(s inet.Stream) {
defer s.Close()
c := s.Conn()
r := ggio.NewDelimitedReader(s, 2048)
mes := pb.Handshake3{}
if err := r.ReadMsg(&mes); err != nil {
log.Errorf("%s error receiving message from %s %s", ProtocolIdentify,
c.RemotePeer(), c.RemoteMultiaddr())
return
}
ids.consumeMessage(&mes, c)
log.Debugf("%s received message from %s %s", ProtocolIdentify,
c.RemotePeer(), c.RemoteMultiaddr())
}
func (ids *IDService) populateMessage(mes *pb.Handshake3, c inet.Conn) {
// set protocols this node is currently handling
protos := ids.Network.Protocols()
mes.Protocols = make([]string, len(protos))
for i, p := range protos {
mes.Protocols[i] = string(p)
}
// observed address so other side is informed of their
// "public" address, at least in relation to us.
mes.ObservedAddr = c.RemoteMultiaddr().Bytes()
// set listen addrs
laddrs, err := ids.Network.InterfaceListenAddresses()
if err != nil {
log.Error(err)
} else {
mes.ListenAddrs = make([][]byte, len(laddrs))
for i, addr := range laddrs {
mes.ListenAddrs[i] = addr.Bytes()
}
log.Debugf("%s sent listen addrs to %s: %s", c.LocalPeer(), c.RemotePeer(), laddrs)
}
// set protocol versions
mes.H1 = handshake.NewHandshake1("", "")
}
func (ids *IDService) consumeMessage(mes *pb.Handshake3, c inet.Conn) {
p := c.RemotePeer()
// mes.Protocols
// mes.ObservedAddr
// mes.ListenAddrs
laddrs := mes.GetListenAddrs()
lmaddrs := make([]ma.Multiaddr, 0, len(laddrs))
for _, addr := range laddrs {
maddr, err := ma.NewMultiaddrBytes(addr)
if err != nil {
log.Errorf("%s failed to parse multiaddr from %s %s", ProtocolIdentify, p,
c.RemoteMultiaddr())
continue
}
lmaddrs = append(lmaddrs, maddr)
}
// update our peerstore with the addresses.
ids.Network.Peerstore().AddAddresses(p, lmaddrs)
log.Debugf("%s received listen addrs for %s: %s", c.LocalPeer(), c.RemotePeer(), lmaddrs)
// get protocol versions
pv := *mes.H1.ProtocolVersion
av := *mes.H1.AgentVersion
ids.Network.Peerstore().Put(p, "ProtocolVersion", pv)
ids.Network.Peerstore().Put(p, "AgentVersion", av)
}
// IdentifyWait returns a channel which will be closed once
// "ProtocolIdentify" (handshake3) finishes on given conn.
// This happens async so the connection can start to be used
// even if handshake3 knowledge is not necesary.
// Users **MUST** call IdentifyWait _after_ IdentifyConn
func (ids *IDService) IdentifyWait(c inet.Conn) <-chan struct{} {
ids.currmu.Lock()
ch, found := ids.currid[c]
ids.currmu.Unlock()
if found {
return ch
}
// if not found, it means we are already done identifying it, or
// haven't even started. either way, return a new channel closed.
ch = make(chan struct{})
close(ch)
return ch
}
package identify_test
import (
"testing"
"time"
inet "github.com/jbenet/go-ipfs/p2p/net"
handshake "github.com/jbenet/go-ipfs/p2p/net/handshake"
netutil "github.com/jbenet/go-ipfs/p2p/net/swarmnet/util"
peer "github.com/jbenet/go-ipfs/p2p/peer"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
)
func subtestIDService(t *testing.T, postDialWait time.Duration) {
// the generated networks should have the id service wired in.
ctx := context.Background()
n1 := netutil.GenNetwork(t, ctx)
n2 := netutil.GenNetwork(t, ctx)
n1p := n1.LocalPeer()
n2p := n2.LocalPeer()
testKnowsAddrs(t, n1, n2p, []ma.Multiaddr{}) // nothing
testKnowsAddrs(t, n2, n1p, []ma.Multiaddr{}) // nothing
// have n2 tell n1, so we can dial...
netutil.DivulgeAddresses(n2, n1)
testKnowsAddrs(t, n1, n2p, n2.Peerstore().Addresses(n2p)) // has them
testKnowsAddrs(t, n2, n1p, []ma.Multiaddr{}) // nothing
if err := n1.DialPeer(ctx, n2p); err != nil {
t.Fatalf("Failed to dial:", err)
}
// we need to wait here if Dial returns before ID service is finished.
if postDialWait > 0 {
<-time.After(postDialWait)
}
// the IDService should be opened automatically, by the network.
// what we should see now is that both peers know about each others listen addresses.
testKnowsAddrs(t, n1, n2p, n2.Peerstore().Addresses(n2p)) // has them
testHasProtocolVersions(t, n1, n2p)
// now, this wait we do have to do. it's the wait for the Listening side
// to be done identifying the connection.
c := n2.ConnsToPeer(n1.LocalPeer())
if len(c) < 1 {
t.Fatal("should have connection by now at least.")
}
<-n2.IdentifyProtocol().IdentifyWait(c[0])
// and the protocol versions.
testKnowsAddrs(t, n2, n1p, n1.Peerstore().Addresses(n1p)) // has them
testHasProtocolVersions(t, n2, n1p)
}
func testKnowsAddrs(t *testing.T, n inet.Network, p peer.ID, expected []ma.Multiaddr) {
actual := n.Peerstore().Addresses(p)
if len(actual) != len(expected) {
t.Error("dont have the same addresses")
}
have := map[string]struct{}{}
for _, addr := range actual {
have[addr.String()] = struct{}{}
}
for _, addr := range expected {
if _, found := have[addr.String()]; !found {
t.Errorf("%s did not have addr for %s: %s", n.LocalPeer(), p, addr)
// panic("ahhhhhhh")
}
}
}
func testHasProtocolVersions(t *testing.T, n inet.Network, p peer.ID) {
v, err := n.Peerstore().Get(p, "ProtocolVersion")
if v == nil {
t.Error("no protocol version")
return
}
if v.(string) != handshake.IpfsVersion.String() {
t.Error("protocol mismatch", err)
}
v, err = n.Peerstore().Get(p, "AgentVersion")
if v.(string) != handshake.ClientVersion {
t.Error("agent version mismatch", err)
}
}
// TestIDServiceWait gives the ID service 100ms to finish after dialing
// this is becasue it used to be concurrent. Now, Dial wait till the
// id service is done.
func TestIDServiceWait(t *testing.T) {
N := 3
for i := 0; i < N; i++ {
subtestIDService(t, 100*time.Millisecond)
}
}
func TestIDServiceNoWait(t *testing.T) {
N := 3
for i := 0; i < N; i++ {
subtestIDService(t, 0)
}
}
package mux
import (
"fmt"
"io"
"sync"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
inet "github.com/jbenet/go-ipfs/p2p/net"
eventlog "github.com/jbenet/go-ipfs/util/eventlog"
lgbl "github.com/jbenet/go-ipfs/util/eventlog/loggables"
)
var log = eventlog.Logger("net/mux")
// Mux provides simple stream multixplexing.
// It helps you precisely when:
// * You have many streams
// * You have function handlers
//
// It contains the handlers for each protocol accepted.
// It dispatches handlers for streams opened by remote peers.
//
// We use a totally ad-hoc encoding:
// <1 byte length in bytes><string name>
// So "bitswap" is 0x0762697473776170
//
// NOTE: only the dialer specifies this muxing line.
// This is because we're using Streams :)
//
// WARNING: this datastructure IS NOT threadsafe.
// do not modify it once the network is using it.
type Mux struct {
Default inet.StreamHandler // handles unknown protocols.
Handlers inet.StreamHandlerMap
sync.RWMutex
}
// Protocols returns the list of protocols this muxer has handlers for
func (m *Mux) Protocols() []inet.ProtocolID {
m.RLock()
l := make([]inet.ProtocolID, 0, len(m.Handlers))
for p := range m.Handlers {
l = append(l, p)
}
m.RUnlock()
return l
}
// ReadProtocolHeader reads the stream and returns the next Handler function
// according to the muxer encoding.
func (m *Mux) ReadProtocolHeader(s io.Reader) (string, inet.StreamHandler, error) {
// log.Error("ReadProtocolHeader")
name, err := ReadLengthPrefix(s)
if err != nil {
return "", nil, err
}
// log.Debug("ReadProtocolHeader got:", name)
m.RLock()
h, found := m.Handlers[inet.ProtocolID(name)]
m.RUnlock()
switch {
case !found && m.Default != nil:
return name, m.Default, nil
case !found && m.Default == nil:
return name, nil, fmt.Errorf("%s no handler with name: %s (%d)", m, name, len(name))
default:
return name, h, nil
}
}
// String returns the muxer's printing representation
func (m *Mux) String() string {
m.RLock()
defer m.RUnlock()
return fmt.Sprintf("<Muxer %p %d>", m, len(m.Handlers))
}
// SetHandler sets the protocol handler on the Network's Muxer.
// This operation is threadsafe.
func (m *Mux) SetHandler(p inet.ProtocolID, h inet.StreamHandler) {
log.Debugf("%s setting handler for protocol: %s (%d)", m, p, len(p))
m.Lock()
m.Handlers[p] = h
m.Unlock()
}
// Handle reads the next name off the Stream, and calls a handler function
// This is done in its own goroutine, to avoid blocking the caller.
func (m *Mux) Handle(s inet.Stream) {
go m.HandleSync(s)
}
// HandleSync reads the next name off the Stream, and calls a handler function
// This is done synchronously. The handler function will return before
// HandleSync returns.
func (m *Mux) HandleSync(s inet.Stream) {
ctx := context.Background()
name, handler, err := m.ReadProtocolHeader(s)
if err != nil {
err = fmt.Errorf("protocol mux error: %s", err)
log.Error(err)
log.Event(ctx, "muxError", lgbl.Error(err))
return
}
log.Infof("muxer handle protocol: %s", name)
log.Event(ctx, "muxHandle", eventlog.Metadata{"protocol": name})
handler(s)
}
// ReadLengthPrefix reads the name from Reader with a length-byte-prefix.
func ReadLengthPrefix(r io.Reader) (string, error) {
// c-string identifier
// the first byte is our length
l := make([]byte, 1)
if _, err := io.ReadFull(r, l); err != nil {
return "", err
}
length := int(l[0])
// the next are our identifier
name := make([]byte, length)
if _, err := io.ReadFull(r, name); err != nil {
return "", err
}
return string(name), nil
}
// WriteLengthPrefix writes the name into Writer with a length-byte-prefix.
func WriteLengthPrefix(w io.Writer, name string) error {
// log.Error("WriteLengthPrefix", name)
s := make([]byte, len(name)+1)
s[0] = byte(len(name))
copy(s[1:], []byte(name))
_, err := w.Write(s)
return err
}
// WriteProtocolHeader defines how a protocol is written into the header of
// a stream. This is so the muxer can multiplex between services.
func WriteProtocolHeader(pr inet.ProtocolID, s inet.Stream) error {
if pr != "" { // only write proper protocol headers
if err := WriteLengthPrefix(s, string(pr)); err != nil {
return err
}
}
return nil
}
package mux
import (
"bytes"
"testing"
inet "github.com/jbenet/go-ipfs/p2p/net"
)
var testCases = map[string]string{
"bitswap": "\u0007bitswap",
"dht": "\u0003dht",
"ipfs": "\u0004ipfs",
"ipfsdksnafkasnfkdajfkdajfdsjadosiaaodjasofdias": ".ipfsdksnafkasnfkdajfkdajfdsjadosiaaodjasofdias",
}
func TestWrite(t *testing.T) {
for k, v := range testCases {
var buf bytes.Buffer
WriteLengthPrefix(&buf, k)
v2 := buf.Bytes()
if !bytes.Equal(v2, []byte(v)) {
t.Errorf("failed: %s - %v != %v", k, []byte(v), v2)
}
}
}
func TestHandler(t *testing.T) {
outs := make(chan string, 10)
h := func(n string) func(s inet.Stream) {
return func(s inet.Stream) {
outs <- n
}
}
m := Mux{Handlers: inet.StreamHandlerMap{}}
m.Default = h("default")
m.Handlers["dht"] = h("bitswap")
// m.Handlers["ipfs"] = h("bitswap") // default!
m.Handlers["bitswap"] = h("bitswap")
m.Handlers["ipfsdksnafkasnfkdajfkdajfdsjadosiaaodjasofdias"] = h("bitswap")
for k, v := range testCases {
var buf bytes.Buffer
if _, err := buf.Write([]byte(v)); err != nil {
t.Error(err)
continue
}
name, _, err := m.ReadProtocolHeader(&buf)
if err != nil {
t.Error(err)
continue
}
if name != k {
t.Errorf("name mismatch: %s != %s", k, name)
continue
}
}
}
package relay
import (
"fmt"
"io"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup"
mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
inet "github.com/jbenet/go-ipfs/p2p/net"
peer "github.com/jbenet/go-ipfs/p2p/peer"
eventlog "github.com/jbenet/go-ipfs/util/eventlog"
)
var log = eventlog.Logger("relay")
// ProtocolRelay is the ProtocolID of the Relay Service.
const ProtocolRelay inet.ProtocolID = "/ipfs/relay"
// Relay is a structure that implements ProtocolRelay.
// It is a simple relay service which forwards traffic
// between two directly connected peers.
//
// the protocol is very simple:
//
// /ipfs/relay\n
// <multihash src id>
// <multihash dst id>
// <data stream>
//
type RelayService struct {
Network inet.Network
handler inet.StreamHandler // for streams sent to us locally.
cg ctxgroup.ContextGroup
}
func NewRelayService(ctx context.Context, n inet.Network, sh inet.StreamHandler) *RelayService {
s := &RelayService{
Network: n,
handler: sh,
cg: ctxgroup.WithContext(ctx),
}
n.SetHandler(inet.ProtocolRelay, s.requestHandler)
return s
}
// requestHandler is the function called by clients
func (rs *RelayService) requestHandler(s inet.Stream) {
if err := rs.handleStream(s); err != nil {
log.Error("RelayService error:", err)
}
}
// handleStream is our own handler, which returns an error for simplicity.
func (rs *RelayService) handleStream(s inet.Stream) error {
defer s.Close()
// read the header (src and dst peer.IDs)
src, dst, err := ReadHeader(s)
if err != nil {
return fmt.Errorf("stream with bad header: %s", err)
}
local := rs.Network.LocalPeer()
switch {
case src == local:
return fmt.Errorf("relaying from self")
case dst == local: // it's for us! yaaay.
log.Debugf("%s consuming stream from %s", rs.Network.LocalPeer(), src)
return rs.consumeStream(s)
default: // src and dst are not local. relay it.
log.Debugf("%s relaying stream %s <--> %s", rs.Network.LocalPeer(), src, dst)
return rs.pipeStream(src, dst, s)
}
}
// consumeStream connects streams directed to the local peer
// to our handler, with the header now stripped (read).
func (rs *RelayService) consumeStream(s inet.Stream) error {
rs.handler(s) // boom.
return nil
}
// pipeStream relays over a stream to a remote peer. It's like `cat`
func (rs *RelayService) pipeStream(src, dst peer.ID, s inet.Stream) error {
s2, err := rs.openStreamToPeer(dst)
if err != nil {
return fmt.Errorf("failed to open stream to peer: %s -- %s", dst, err)
}
if err := WriteHeader(s2, src, dst); err != nil {
return err
}
// connect the series of tubes.
done := make(chan retio, 2)
go func() {
n, err := io.Copy(s2, s)
done <- retio{n, err}
}()
go func() {
n, err := io.Copy(s, s2)
done <- retio{n, err}
}()
r1 := <-done
r2 := <-done
log.Infof("relayed %d/%d bytes between %s and %s", r1.n, r2.n, src, dst)
if r1.err != nil {
return r1.err
}
return r2.err
}
// openStreamToPeer opens a pipe to a remote endpoint
// for now, can only open streams to directly connected peers.
// maybe we can do some routing later on.
func (rs *RelayService) openStreamToPeer(p peer.ID) (inet.Stream, error) {
return rs.Network.NewStream(ProtocolRelay, p)
}
func ReadHeader(r io.Reader) (src, dst peer.ID, err error) {
mhr := mh.NewReader(r)
s, err := mhr.ReadMultihash()
if err != nil {
return "", "", err
}
d, err := mhr.ReadMultihash()
if err != nil {
return "", "", err
}
return peer.ID(s), peer.ID(d), nil
}
func WriteHeader(w io.Writer, src, dst peer.ID) error {
// write header to w.
mhw := mh.NewWriter(w)
if err := mhw.WriteMultihash(mh.Multihash(src)); err != nil {
return fmt.Errorf("failed to write relay header: %s -- %s", dst, err)
}
if err := mhw.WriteMultihash(mh.Multihash(dst)); err != nil {
return fmt.Errorf("failed to write relay header: %s -- %s", dst, err)
}
return nil
}
type retio struct {
n int64
err error
}
package relay_test
import (
"io"
"testing"
inet "github.com/jbenet/go-ipfs/p2p/net"
mux "github.com/jbenet/go-ipfs/p2p/net/services/mux"
relay "github.com/jbenet/go-ipfs/p2p/net/services/relay"
netutil "github.com/jbenet/go-ipfs/p2p/net/swarmnet/util"
eventlog "github.com/jbenet/go-ipfs/util/eventlog"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
)
var log = eventlog.Logger("relay_test")
func TestRelaySimple(t *testing.T) {
ctx := context.Background()
// these networks have the relay service wired in already.
n1 := netutil.GenNetwork(t, ctx)
n2 := netutil.GenNetwork(t, ctx)
n3 := netutil.GenNetwork(t, ctx)
n1p := n1.LocalPeer()
n2p := n2.LocalPeer()
n3p := n3.LocalPeer()
netutil.DivulgeAddresses(n2, n1)
netutil.DivulgeAddresses(n2, n3)
if err := n1.DialPeer(ctx, n2p); err != nil {
t.Fatalf("Failed to dial:", err)
}
if err := n3.DialPeer(ctx, n2p); err != nil {
t.Fatalf("Failed to dial:", err)
}
// setup handler on n3 to copy everything over to the pipe.
piper, pipew := io.Pipe()
n3.SetHandler(inet.ProtocolTesting, func(s inet.Stream) {
log.Debug("relay stream opened to n3!")
log.Debug("piping and echoing everything")
w := io.MultiWriter(s, pipew)
io.Copy(w, s)
log.Debug("closing stream")
s.Close()
})
// ok, now we can try to relay n1--->n2--->n3.
log.Debug("open relay stream")
s, err := n1.NewStream(relay.ProtocolRelay, n2p)
if err != nil {
t.Fatal(err)
}
// ok first thing we write the relay header n1->n3
log.Debug("write relay header")
if err := relay.WriteHeader(s, n1p, n3p); err != nil {
t.Fatal(err)
}
// ok now the header's there, we can write the next protocol header.
log.Debug("write testing header")
if err := mux.WriteProtocolHeader(inet.ProtocolTesting, s); err != nil {
t.Fatal(err)
}
// okay, now we should be able to write text, and read it out.
buf1 := []byte("abcdefghij")
buf2 := make([]byte, 10)
buf3 := make([]byte, 10)
log.Debug("write in some text.")
if _, err := s.Write(buf1); err != nil {
t.Fatal(err)
}
// read it out from the pipe.
log.Debug("read it out from the pipe.")
if _, err := io.ReadFull(piper, buf2); err != nil {
t.Fatal(err)
}
if string(buf1) != string(buf2) {
t.Fatal("should've gotten that text out of the pipe")
}
// read it out from the stream (echoed)
log.Debug("read it out from the stream (echoed).")
if _, err := io.ReadFull(s, buf3); err != nil {
t.Fatal(err)
}
if string(buf1) != string(buf3) {
t.Fatal("should've gotten that text out of the stream")
}
// sweet. relay works.
log.Debug("sweet, relay works.")
s.Close()
}
func TestRelayAcrossFour(t *testing.T) {
ctx := context.Background()
// these networks have the relay service wired in already.
n1 := netutil.GenNetwork(t, ctx)
n2 := netutil.GenNetwork(t, ctx)
n3 := netutil.GenNetwork(t, ctx)
n4 := netutil.GenNetwork(t, ctx)
n5 := netutil.GenNetwork(t, ctx)
n1p := n1.LocalPeer()
n2p := n2.LocalPeer()
n3p := n3.LocalPeer()
n4p := n4.LocalPeer()
n5p := n5.LocalPeer()
netutil.DivulgeAddresses(n2, n1)
netutil.DivulgeAddresses(n2, n3)
netutil.DivulgeAddresses(n4, n3)
netutil.DivulgeAddresses(n4, n5)
if err := n1.DialPeer(ctx, n2p); err != nil {
t.Fatalf("Failed to dial:", err)
}
if err := n3.DialPeer(ctx, n2p); err != nil {
t.Fatalf("Failed to dial:", err)
}
if err := n3.DialPeer(ctx, n4p); err != nil {
t.Fatalf("Failed to dial:", err)
}
if err := n5.DialPeer(ctx, n4p); err != nil {
t.Fatalf("Failed to dial:", err)
}
// setup handler on n5 to copy everything over to the pipe.
piper, pipew := io.Pipe()
n5.SetHandler(inet.ProtocolTesting, func(s inet.Stream) {
log.Debug("relay stream opened to n5!")
log.Debug("piping and echoing everything")
w := io.MultiWriter(s, pipew)
io.Copy(w, s)
log.Debug("closing stream")
s.Close()
})
// ok, now we can try to relay n1--->n2--->n3--->n4--->n5
log.Debug("open relay stream")
s, err := n1.NewStream(relay.ProtocolRelay, n2p)
if err != nil {
t.Fatal(err)
}
log.Debugf("write relay header n1->n3 (%s -> %s)", n1p, n3p)
if err := relay.WriteHeader(s, n1p, n3p); err != nil {
t.Fatal(err)
}
log.Debugf("write relay header n1->n4 (%s -> %s)", n1p, n4p)
if err := mux.WriteProtocolHeader(relay.ProtocolRelay, s); err != nil {
t.Fatal(err)
}
if err := relay.WriteHeader(s, n1p, n4p); err != nil {
t.Fatal(err)
}
log.Debugf("write relay header n1->n5 (%s -> %s)", n1p, n5p)
if err := mux.WriteProtocolHeader(relay.ProtocolRelay, s); err != nil {
t.Fatal(err)
}
if err := relay.WriteHeader(s, n1p, n5p); err != nil {
t.Fatal(err)
}
// ok now the header's there, we can write the next protocol header.
log.Debug("write testing header")
if err := mux.WriteProtocolHeader(inet.ProtocolTesting, s); err != nil {
t.Fatal(err)
}
// okay, now we should be able to write text, and read it out.
buf1 := []byte("abcdefghij")
buf2 := make([]byte, 10)
buf3 := make([]byte, 10)
log.Debug("write in some text.")
if _, err := s.Write(buf1); err != nil {
t.Fatal(err)
}
// read it out from the pipe.
log.Debug("read it out from the pipe.")
if _, err := io.ReadFull(piper, buf2); err != nil {
t.Fatal(err)
}
if string(buf1) != string(buf2) {
t.Fatal("should've gotten that text out of the pipe")
}
// read it out from the stream (echoed)
log.Debug("read it out from the stream (echoed).")
if _, err := io.ReadFull(s, buf3); err != nil {
t.Fatal(err)
}
if string(buf1) != string(buf3) {
t.Fatal("should've gotten that text out of the stream")
}
// sweet. relay works.
log.Debug("sweet, relaying across 4 works.")
s.Close()
}
func TestRelayStress(t *testing.T) {
buflen := 1 << 18
iterations := 10
ctx := context.Background()
// these networks have the relay service wired in already.
n1 := netutil.GenNetwork(t, ctx)
n2 := netutil.GenNetwork(t, ctx)
n3 := netutil.GenNetwork(t, ctx)
n1p := n1.LocalPeer()
n2p := n2.LocalPeer()
n3p := n3.LocalPeer()
netutil.DivulgeAddresses(n2, n1)
netutil.DivulgeAddresses(n2, n3)
if err := n1.DialPeer(ctx, n2p); err != nil {
t.Fatalf("Failed to dial:", err)
}
if err := n3.DialPeer(ctx, n2p); err != nil {
t.Fatalf("Failed to dial:", err)
}
// setup handler on n3 to copy everything over to the pipe.
piper, pipew := io.Pipe()
n3.SetHandler(inet.ProtocolTesting, func(s inet.Stream) {
log.Debug("relay stream opened to n3!")
log.Debug("piping and echoing everything")
w := io.MultiWriter(s, pipew)
io.Copy(w, s)
log.Debug("closing stream")
s.Close()
})
// ok, now we can try to relay n1--->n2--->n3.
log.Debug("open relay stream")
s, err := n1.NewStream(relay.ProtocolRelay, n2p)
if err != nil {
t.Fatal(err)
}
// ok first thing we write the relay header n1->n3
log.Debug("write relay header")
if err := relay.WriteHeader(s, n1p, n3p); err != nil {
t.Fatal(err)
}
// ok now the header's there, we can write the next protocol header.
log.Debug("write testing header")
if err := mux.WriteProtocolHeader(inet.ProtocolTesting, s); err != nil {
t.Fatal(err)
}
// okay, now write lots of text and read it back out from both
// the pipe and the stream.
buf1 := make([]byte, buflen)
buf2 := make([]byte, len(buf1))
buf3 := make([]byte, len(buf1))
fillbuf := func(buf []byte, b byte) {
for i := range buf {
buf[i] = b
}
}
for i := 0; i < iterations; i++ {
fillbuf(buf1, byte(int('a')+i))
log.Debugf("writing %d bytes (%d/%d)", len(buf1), i, iterations)
if _, err := s.Write(buf1); err != nil {
t.Fatal(err)
}
log.Debug("read it out from the pipe.")
if _, err := io.ReadFull(piper, buf2); err != nil {
t.Fatal(err)
}
if string(buf1) != string(buf2) {
t.Fatal("should've gotten that text out of the pipe")
}
// read it out from the stream (echoed)
log.Debug("read it out from the stream (echoed).")
if _, err := io.ReadFull(s, buf3); err != nil {
t.Fatal(err)
}
if string(buf1) != string(buf3) {
t.Fatal("should've gotten that text out of the stream")
}
}
log.Debug("sweet, relay works under stress.")
s.Close()
}
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
package swarm package swarm
import ( import (
inet "github.com/jbenet/go-ipfs/p2p/net"
peer "github.com/jbenet/go-ipfs/p2p/peer" peer "github.com/jbenet/go-ipfs/p2p/peer"
eventlog "github.com/jbenet/go-ipfs/util/eventlog" eventlog "github.com/jbenet/go-ipfs/util/eventlog"
...@@ -92,7 +93,7 @@ func (s *Swarm) SetConnHandler(handler ConnHandler) { ...@@ -92,7 +93,7 @@ func (s *Swarm) SetConnHandler(handler ConnHandler) {
// SetStreamHandler assigns the handler for new streams. // SetStreamHandler assigns the handler for new streams.
// See peerstream. // See peerstream.
func (s *Swarm) SetStreamHandler(handler StreamHandler) { func (s *Swarm) SetStreamHandler(handler inet.StreamHandler) {
s.swarm.SetStreamHandler(func(s *ps.Stream) { s.swarm.SetStreamHandler(func(s *ps.Stream) {
handler(wrapStream(s)) handler(wrapStream(s))
}) })
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
ic "github.com/jbenet/go-ipfs/p2p/crypto" ic "github.com/jbenet/go-ipfs/p2p/crypto"
inet "github.com/jbenet/go-ipfs/p2p/net"
conn "github.com/jbenet/go-ipfs/p2p/net/conn" conn "github.com/jbenet/go-ipfs/p2p/net/conn"
peer "github.com/jbenet/go-ipfs/p2p/peer" peer "github.com/jbenet/go-ipfs/p2p/peer"
...@@ -74,12 +75,18 @@ func (c *Conn) RemotePublicKey() ic.PubKey { ...@@ -74,12 +75,18 @@ func (c *Conn) RemotePublicKey() ic.PubKey {
return c.RawConn().RemotePublicKey() return c.RawConn().RemotePublicKey()
} }
// NewStream returns a new Stream from this connection // NewSwarmStream returns a new Stream from this connection
func (c *Conn) NewStream() (*Stream, error) { func (c *Conn) NewSwarmStream() (*Stream, error) {
s, err := c.StreamConn().NewStream() s, err := c.StreamConn().NewStream()
return wrapStream(s), err return wrapStream(s), err
} }
// NewStream returns a new Stream from this connection
func (c *Conn) NewStream() (inet.Stream, error) {
s, err := c.NewSwarmStream()
return inet.Stream(s), err
}
func (c *Conn) Close() error { func (c *Conn) Close() error {
return c.StreamConn().Close() return c.StreamConn().Close()
} }
......
...@@ -5,7 +5,7 @@ import ( ...@@ -5,7 +5,7 @@ import (
peer "github.com/jbenet/go-ipfs/p2p/peer" peer "github.com/jbenet/go-ipfs/p2p/peer"
inet "github.com/jbenet/go-ipfs/p2p/net2" inet "github.com/jbenet/go-ipfs/p2p/net"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context" context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup" ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup"
......
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context" context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
inet "github.com/jbenet/go-ipfs/p2p/net" inet "github.com/jbenet/go-ipfs/p2p/net"
netutil "github.com/jbenet/go-ipfs/p2p/net/swarmnet/util" testutil "github.com/jbenet/go-ipfs/p2p/test/util"
) )
// TestConnectednessCorrect starts a few networks, connects a few // TestConnectednessCorrect starts a few networks, connects a few
...@@ -19,14 +19,14 @@ func TestConnectednessCorrect(t *testing.T) { ...@@ -19,14 +19,14 @@ func TestConnectednessCorrect(t *testing.T) {
nets := make([]inet.Network, 4) nets := make([]inet.Network, 4)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
nets[i] = netutil.GenNetwork(t, ctx) nets[i] = testutil.GenSwarmNetwork(t, ctx)
} }
// connect 0-1, 0-2, 0-3, 1-2, 2-3 // connect 0-1, 0-2, 0-3, 1-2, 2-3
dial := func(a, b inet.Network) { dial := func(a, b inet.Network) {
netutil.DivulgeAddresses(b, a) testutil.DivulgeAddresses(b, a)
if err := a.DialPeer(ctx, b.LocalPeer()); err != nil { if _, err := a.DialPeer(ctx, b.LocalPeer()); err != nil {
t.Fatalf("Failed to dial: %s", err) t.Fatalf("Failed to dial: %s", err)
} }
} }
......
package swarm package swarm
import ( import (
inet "github.com/jbenet/go-ipfs/p2p/net"
ps "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-peerstream" ps "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-peerstream"
) )
...@@ -8,17 +10,18 @@ import ( ...@@ -8,17 +10,18 @@ import (
// our Conn and Swarm (instead of just the ps.Conn and ps.Swarm) // our Conn and Swarm (instead of just the ps.Conn and ps.Swarm)
type Stream ps.Stream type Stream ps.Stream
// StreamHandler is called when new streams are opened from remote peers.
// See peerstream.StreamHandler
type StreamHandler func(*Stream)
// Stream returns the underlying peerstream.Stream // Stream returns the underlying peerstream.Stream
func (s *Stream) Stream() *ps.Stream { func (s *Stream) Stream() *ps.Stream {
return (*ps.Stream)(s) return (*ps.Stream)(s)
} }
// Conn returns the Conn associated with this Stream // Conn returns the Conn associated with this Stream, as an inet.Conn
func (s *Stream) Conn() *Conn { func (s *Stream) Conn() inet.Conn {
return s.SwarmConn()
}
// SwarmConn returns the Conn associated with this Stream, as a *Conn
func (s *Stream) SwarmConn() *Conn {
return (*Conn)(s.Stream().Conn()) return (*Conn)(s.Stream().Conn())
} }
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"testing" "testing"
"time" "time"
inet "github.com/jbenet/go-ipfs/p2p/net"
peer "github.com/jbenet/go-ipfs/p2p/peer" peer "github.com/jbenet/go-ipfs/p2p/peer"
errors "github.com/jbenet/go-ipfs/util/debugerror" errors "github.com/jbenet/go-ipfs/util/debugerror"
testutil "github.com/jbenet/go-ipfs/util/testutil" testutil "github.com/jbenet/go-ipfs/util/testutil"
...@@ -15,12 +16,12 @@ import ( ...@@ -15,12 +16,12 @@ import (
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
) )
func EchoStreamHandler(stream *Stream) { func EchoStreamHandler(stream inet.Stream) {
go func() { go func() {
defer stream.Close() defer stream.Close()
// pull out the ipfs conn // pull out the ipfs conn
c := stream.Conn().RawConn() c := stream.Conn()
log.Debugf("%s ponging to %s", c.LocalPeer(), c.RemotePeer()) log.Debugf("%s ponging to %s", c.LocalPeer(), c.RemotePeer())
buf := make([]byte, 4) buf := make([]byte, 4)
......
// Package net provides an interface for ipfs to interact with the network through
package net
import (
"fmt"
ic "github.com/jbenet/go-ipfs/p2p/crypto"
peer "github.com/jbenet/go-ipfs/p2p/peer"
inet "github.com/jbenet/go-ipfs/p2p/net"
ids "github.com/jbenet/go-ipfs/p2p/net/services/identify"
mux "github.com/jbenet/go-ipfs/p2p/net/services/mux"
relay "github.com/jbenet/go-ipfs/p2p/net/services/relay"
swarm "github.com/jbenet/go-ipfs/p2p/net/swarm"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup"
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
eventlog "github.com/jbenet/go-ipfs/util/eventlog"
)
var log = eventlog.Logger("net/mux")
type stream swarm.Stream
func (s *stream) SwarmStream() *swarm.Stream {
return (*swarm.Stream)(s)
}
// Conn returns the connection this stream is part of.
func (s *stream) Conn() inet.Conn {
c := s.SwarmStream().Conn()
return (*conn_)(c)
}
// Conn returns the connection this stream is part of.
func (s *stream) Close() error {
return s.SwarmStream().Close()
}
// Read reads bytes from a stream.
func (s *stream) Read(p []byte) (n int, err error) {
return s.SwarmStream().Read(p)
}
// Write writes bytes to a stream, flushing for each call.
func (s *stream) Write(p []byte) (n int, err error) {
return s.SwarmStream().Write(p)
}
type conn_ swarm.Conn
func (s *conn_) String() string {
return s.SwarmConn().String()
}
func (c *conn_) SwarmConn() *swarm.Conn {
return (*swarm.Conn)(c)
}
func (c *conn_) NewStreamWithProtocol(pr inet.ProtocolID) (inet.Stream, error) {
s, err := (*swarm.Conn)(c).NewStream()
if err != nil {
return nil, err
}
ss := (*stream)(s)
if err := mux.WriteProtocolHeader(pr, ss); err != nil {
ss.Close()
return nil, err
}
return ss, nil
}
func (c *conn_) LocalMultiaddr() ma.Multiaddr {
return c.SwarmConn().LocalMultiaddr()
}
func (c *conn_) RemoteMultiaddr() ma.Multiaddr {
return c.SwarmConn().RemoteMultiaddr()
}
func (c *conn_) LocalPeer() peer.ID {
return c.SwarmConn().LocalPeer()
}
func (c *conn_) RemotePeer() peer.ID {
return c.SwarmConn().RemotePeer()
}
func (c *conn_) LocalPrivateKey() ic.PrivKey {
return c.SwarmConn().LocalPrivateKey()
}
func (c *conn_) RemotePublicKey() ic.PubKey {
return c.SwarmConn().RemotePublicKey()
}
// Network implements the inet.Network interface.
// It uses a swarm to connect to remote hosts.
type Network struct {
local peer.ID // local peer
ps peer.Peerstore
swarm *swarm.Swarm // peer connection multiplexing
mux mux.Mux // protocol multiplexing
ids *ids.IDService
relay *relay.RelayService
cg ctxgroup.ContextGroup // for Context closing
}
// NewNetwork constructs a new network and starts listening on given addresses.
func NewNetwork(ctx context.Context, listen []ma.Multiaddr, local peer.ID,
peers peer.Peerstore) (*Network, error) {
s, err := swarm.NewSwarm(ctx, listen, local, peers)
if err != nil {
return nil, err
}
n := &Network{
local: local,
swarm: s,
mux: mux.Mux{Handlers: inet.StreamHandlerMap{}},
cg: ctxgroup.WithContext(ctx),
ps: peers,
}
n.cg.SetTeardown(n.close)
n.cg.AddChildGroup(s.CtxGroup())
s.SetStreamHandler(func(s *swarm.Stream) {
n.mux.Handle((*stream)(s))
})
// setup ProtocolIdentify to immediately "asks the other side about them"
n.ids = ids.NewIDService(n)
s.SetConnHandler(n.newConnHandler)
// setup ProtocolRelay to allow traffic relaying.
// Feed things we get for ourselves into the muxer.
n.relay = relay.NewRelayService(n.cg.Context(), n, n.mux.HandleSync)
return n, nil
}
func (n *Network) newConnHandler(c *swarm.Conn) {
cc := (*conn_)(c)
n.ids.IdentifyConn(cc)
}
// DialPeer attempts to establish a connection to a given peer.
// Respects the context.
func (n *Network) DialPeer(ctx context.Context, p peer.ID) error {
log.Debugf("[%s] network dialing peer [%s]", n.local, p)
sc, err := n.swarm.Dial(ctx, p)
if err != nil {
return err
}
// identify the connection before returning.
done := make(chan struct{})
go func() {
n.ids.IdentifyConn((*conn_)(sc))
close(done)
}()
// respect don contexteone
select {
case <-done:
case <-ctx.Done():
return ctx.Err()
}
log.Debugf("network for %s finished dialing %s", n.local, p)
return nil
}
// Protocols returns the ProtocolIDs of all the registered handlers.
func (n *Network) Protocols() []inet.ProtocolID {
return n.mux.Protocols()
}
// CtxGroup returns the network's ContextGroup
func (n *Network) CtxGroup() ctxgroup.ContextGroup {
return n.cg
}
// Swarm returns the network's peerstream.Swarm
func (n *Network) Swarm() *swarm.Swarm {
return n.Swarm()
}
// LocalPeer the network's LocalPeer
func (n *Network) LocalPeer() peer.ID {
return n.swarm.LocalPeer()
}
// Peers returns the connected peers
func (n *Network) Peers() []peer.ID {
return n.swarm.Peers()
}
// Peers returns the connected peers
func (n *Network) Peerstore() peer.Peerstore {
return n.ps
}
// Conns returns the connected peers
func (n *Network) Conns() []inet.Conn {
conns1 := n.swarm.Connections()
out := make([]inet.Conn, len(conns1))
for i, c := range conns1 {
out[i] = (*conn_)(c)
}
return out
}
// ConnsToPeer returns the connections in this Netowrk for given peer.
func (n *Network) ConnsToPeer(p peer.ID) []inet.Conn {
conns1 := n.swarm.ConnectionsToPeer(p)
out := make([]inet.Conn, len(conns1))
for i, c := range conns1 {
out[i] = (*conn_)(c)
}
return out
}
// ClosePeer connection to peer
func (n *Network) ClosePeer(p peer.ID) error {
return n.swarm.CloseConnection(p)
}
// close is the real teardown function
func (n *Network) close() error {
return n.swarm.Close()
}
// Close calls the ContextCloser func
func (n *Network) Close() error {
return n.cg.Close()
}
// BandwidthTotals returns the total amount of bandwidth transferred
func (n *Network) BandwidthTotals() (in uint64, out uint64) {
// need to implement this. probably best to do it in swarm this time.
// need a "metrics" object
return 0, 0
}
// ListenAddresses returns a list of addresses at which this network listens.
func (n *Network) ListenAddresses() []ma.Multiaddr {
return n.swarm.ListenAddresses()
}
// InterfaceListenAddresses returns a list of addresses at which this network
// listens. It expands "any interface" addresses (/ip4/0.0.0.0, /ip6/::) to
// use the known local interfaces.
func (n *Network) InterfaceListenAddresses() ([]ma.Multiaddr, error) {
return swarm.InterfaceListenAddresses(n.swarm)
}
// Connectedness returns a state signaling connection capabilities
// For now only returns Connected || NotConnected. Expand into more later.
func (n *Network) Connectedness(p peer.ID) inet.Connectedness {
c := n.swarm.ConnectionsToPeer(p)
if c != nil && len(c) > 0 {
return inet.Connected
}
return inet.NotConnected
}
// NewStream returns a new stream to given peer p.
// If there is no connection to p, attempts to create one.
// If ProtocolID is "", writes no header.
func (n *Network) NewStream(pr inet.ProtocolID, p peer.ID) (inet.Stream, error) {
log.Debugf("[%s] network opening stream to peer [%s]: %s", n.local, p, pr)
s, err := n.swarm.NewStreamWithPeer(p)
if err != nil {
return nil, err
}
ss := (*stream)(s)
if err := mux.WriteProtocolHeader(pr, ss); err != nil {
ss.Close()
return nil, err
}
return ss, nil
}
// SetHandler sets the protocol handler on the Network's Muxer.
// This operation is threadsafe.
func (n *Network) SetHandler(p inet.ProtocolID, h inet.StreamHandler) {
n.mux.SetHandler(p, h)
}
// String returns a string representation of Network.
func (n *Network) String() string {
return fmt.Sprintf("<Network %s>", n.LocalPeer())
}
// IdentifyProtocol returns the network's IDService
func (n *Network) IdentifyProtocol() *ids.IDService {
return n.ids
}
package net_test
import (
"fmt"
"testing"
"time"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
inet "github.com/jbenet/go-ipfs/p2p/net"
netutil "github.com/jbenet/go-ipfs/p2p/net/swarmnet/util"
)
// TestConnectednessCorrect starts a few networks, connects a few
// and tests Connectedness value is correct.
func TestConnectednessCorrect(t *testing.T) {
ctx := context.Background()
nets := make([]inet.Network, 4)
for i := 0; i < 4; i++ {
nets[i] = netutil.GenNetwork(t, ctx)
}
// connect 0-1, 0-2, 0-3, 1-2, 2-3
dial := func(a, b inet.Network) {
netutil.DivulgeAddresses(b, a)
if err := a.DialPeer(ctx, b.LocalPeer()); err != nil {
t.Fatalf("Failed to dial: %s", err)
}
}
dial(nets[0], nets[1])
dial(nets[0], nets[3])
dial(nets[1], nets[2])
dial(nets[3], nets[2])
// there's something wrong with dial, i think. it's not finishing
// completely. there must be some async stuff.
<-time.After(100 * time.Millisecond)
// test those connected show up correctly
// test connected
expectConnectedness(t, nets[0], nets[1], inet.Connected)
expectConnectedness(t, nets[0], nets[3], inet.Connected)
expectConnectedness(t, nets[1], nets[2], inet.Connected)
expectConnectedness(t, nets[3], nets[2], inet.Connected)
// test not connected
expectConnectedness(t, nets[0], nets[2], inet.NotConnected)
expectConnectedness(t, nets[1], nets[3], inet.NotConnected)
for _, n := range nets {
n.Close()
}
}
func expectConnectedness(t *testing.T, a, b inet.Network, expected inet.Connectedness) {
es := "%s is connected to %s, but Connectedness incorrect. %s %s"
if a.Connectedness(b.LocalPeer()) != expected {
t.Errorf(es, a, b, printConns(a), printConns(b))
}
// test symmetric case
if b.Connectedness(a.LocalPeer()) != expected {
t.Errorf(es, b, a, printConns(b), printConns(a))
}
}
func printConns(n inet.Network) string {
s := fmt.Sprintf("Connections in %s:\n", n)
for _, c := range n.Conns() {
s = s + fmt.Sprintf("- %s\n", c)
}
return s
}
package testutil
import (
"testing"
inet "github.com/jbenet/go-ipfs/p2p/net"
sn "github.com/jbenet/go-ipfs/p2p/net/swarmnet"
peer "github.com/jbenet/go-ipfs/p2p/peer"
tu "github.com/jbenet/go-ipfs/util/testutil"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
)
func GenNetwork(t *testing.T, ctx context.Context) *sn.Network {
p := tu.RandPeerNetParamsOrFatal(t)
ps := peer.NewPeerstore()
ps.AddAddress(p.ID, p.Addr)
ps.AddPubKey(p.ID, p.PubKey)
ps.AddPrivKey(p.ID, p.PrivKey)
n, err := sn.NewNetwork(ctx, ps.Addresses(p.ID), p.ID, ps)
if err != nil {
t.Fatal(err)
}
return n
}
func DivulgeAddresses(a, b inet.Network) {
id := a.LocalPeer()
addrs := a.Peerstore().Addresses(id)
b.Peerstore().AddAddresses(id, addrs)
}
# Network
The IPFS Network package handles all of the peer-to-peer networking. It connects to other hosts, it encrypts communications, it muxes messages between the network's client services and target hosts. It has multiple subcomponents:
- `Conn` - a connection to a single Peer
- `MultiConn` - a set of connections to a single Peer
- `SecureConn` - an encrypted (tls-like) connection
- `Swarm` - holds connections to Peers, multiplexes from/to each `MultiConn`
- `Muxer` - multiplexes between `Services` and `Swarm`. Handles `Requet/Reply`.
- `Service` - connects between an outside client service and Network.
- `Handler` - the client service part that handles requests
It looks a bit like this:
<center>
![](https://docs.google.com/drawings/d/1FvU7GImRsb9GvAWDDo1le85jIrnFJNVB_OTPXC15WwM/pub?h=480)
</center>
package conn
import (
"fmt"
"net"
"time"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
msgio "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-msgio"
mpool "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-msgio/mpool"
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
ic "github.com/jbenet/go-ipfs/p2p/crypto"
peer "github.com/jbenet/go-ipfs/p2p/peer"
u "github.com/jbenet/go-ipfs/util"
eventlog "github.com/jbenet/go-ipfs/util/eventlog"
)
var log = eventlog.Logger("conn")
// ReleaseBuffer puts the given byte array back into the buffer pool,
// first verifying that it is the correct size
func ReleaseBuffer(b []byte) {
log.Debugf("Releasing buffer! (cap,size = %d, %d)", cap(b), len(b))
mpool.ByteSlicePool.Put(uint32(cap(b)), b)
}
// singleConn represents a single connection to another Peer (IPFS Node).
type singleConn struct {
local peer.ID
remote peer.ID
maconn manet.Conn
msgrw msgio.ReadWriteCloser
}
// newConn constructs a new connection
func newSingleConn(ctx context.Context, local, remote peer.ID, maconn manet.Conn) (Conn, error) {
conn := &singleConn{
local: local,
remote: remote,
maconn: maconn,
msgrw: msgio.NewReadWriter(maconn),
}
log.Debugf("newSingleConn %p: %v to %v", conn, local, remote)
return conn, nil
}
// close is the internal close function, called by ContextCloser.Close
func (c *singleConn) Close() error {
log.Debugf("%s closing Conn with %s", c.local, c.remote)
// close underlying connection
return c.msgrw.Close()
}
// ID is an identifier unique to this connection.
func (c *singleConn) ID() string {
return ID(c)
}
func (c *singleConn) String() string {
return String(c, "singleConn")
}
func (c *singleConn) LocalAddr() net.Addr {
return c.maconn.LocalAddr()
}
func (c *singleConn) RemoteAddr() net.Addr {
return c.maconn.RemoteAddr()
}
func (c *singleConn) LocalPrivateKey() ic.PrivKey {
return nil
}
func (c *singleConn) RemotePublicKey() ic.PubKey {
return nil
}
func (c *singleConn) SetDeadline(t time.Time) error {
return c.maconn.SetDeadline(t)
}
func (c *singleConn) SetReadDeadline(t time.Time) error {
return c.maconn.SetReadDeadline(t)
}
func (c *singleConn) SetWriteDeadline(t time.Time) error {
return c.maconn.SetWriteDeadline(t)
}
// LocalMultiaddr is the Multiaddr on this side
func (c *singleConn) LocalMultiaddr() ma.Multiaddr {
return c.maconn.LocalMultiaddr()
}
// RemoteMultiaddr is the Multiaddr on the remote side
func (c *singleConn) RemoteMultiaddr() ma.Multiaddr {
return c.maconn.RemoteMultiaddr()
}
// LocalPeer is the Peer on this side
func (c *singleConn) LocalPeer() peer.ID {
return c.local
}
// RemotePeer is the Peer on the remote side
func (c *singleConn) RemotePeer() peer.ID {
return c.remote
}
// Read reads data, net.Conn style
func (c *singleConn) Read(buf []byte) (int, error) {
return c.msgrw.Read(buf)
}
// Write writes data, net.Conn style
func (c *singleConn) Write(buf []byte) (int, error) {
return c.msgrw.Write(buf)
}
func (c *singleConn) NextMsgLen() (int, error) {
return c.msgrw.NextMsgLen()
}
// ReadMsg reads data, net.Conn style
func (c *singleConn) ReadMsg() ([]byte, error) {
return c.msgrw.ReadMsg()
}
// WriteMsg writes data, net.Conn style
func (c *singleConn) WriteMsg(buf []byte) error {
return c.msgrw.WriteMsg(buf)
}
// ReleaseMsg releases a buffer
func (c *singleConn) ReleaseMsg(m []byte) {
c.msgrw.ReleaseMsg(m)
}
// ID returns the ID of a given Conn.
func ID(c Conn) string {
l := fmt.Sprintf("%s/%s", c.LocalMultiaddr(), c.LocalPeer().Pretty())
r := fmt.Sprintf("%s/%s", c.RemoteMultiaddr(), c.RemotePeer().Pretty())
lh := u.Hash([]byte(l))
rh := u.Hash([]byte(r))
ch := u.XOR(lh, rh)
return u.Key(ch).Pretty()
}
// String returns the user-friendly String representation of a conn
func String(c Conn, typ string) string {
return fmt.Sprintf("%s (%s) <-- %s %p --> (%s) %s",
c.LocalPeer(), c.LocalMultiaddr(), typ, c, c.RemoteMultiaddr(), c.RemotePeer())
}
package conn
import (
"bytes"
"fmt"
"os"
"runtime"
"sync"
"testing"
"time"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
)
func testOneSendRecv(t *testing.T, c1, c2 Conn) {
log.Debugf("testOneSendRecv from %s to %s", c1.LocalPeer(), c2.LocalPeer())
m1 := []byte("hello")
if err := c1.WriteMsg(m1); err != nil {
t.Fatal(err)
}
m2, err := c2.ReadMsg()
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(m1, m2) {
t.Fatal("failed to send: %s %s", m1, m2)
}
}
func testNotOneSendRecv(t *testing.T, c1, c2 Conn) {
m1 := []byte("hello")
if err := c1.WriteMsg(m1); err == nil {
t.Fatal("write should have failed", err)
}
_, err := c2.ReadMsg()
if err == nil {
t.Fatal("read should have failed", err)
}
}
func TestClose(t *testing.T) {
// t.Skip("Skipping in favor of another test")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
c1, c2, _, _ := setupSingleConn(t, ctx)
testOneSendRecv(t, c1, c2)
testOneSendRecv(t, c2, c1)
c1.Close()
testNotOneSendRecv(t, c1, c2)
c2.Close()
testNotOneSendRecv(t, c2, c1)
testNotOneSendRecv(t, c1, c2)
}
func TestCloseLeak(t *testing.T) {
// t.Skip("Skipping in favor of another test")
if testing.Short() {
t.SkipNow()
}
if os.Getenv("TRAVIS") == "true" {
t.Skip("this doesn't work well on travis")
}
var wg sync.WaitGroup
runPair := func(num int) {
ctx, cancel := context.WithCancel(context.Background())
c1, c2, _, _ := setupSingleConn(t, ctx)
for i := 0; i < num; i++ {
b1 := []byte(fmt.Sprintf("beep%d", i))
c1.WriteMsg(b1)
b2, err := c2.ReadMsg()
if err != nil {
panic(err)
}
if !bytes.Equal(b1, b2) {
panic(fmt.Errorf("bytes not equal: %s != %s", b1, b2))
}
b2 = []byte(fmt.Sprintf("boop%d", i))
c2.WriteMsg(b2)
b1, err = c1.ReadMsg()
if err != nil {
panic(err)
}
if !bytes.Equal(b1, b2) {
panic(fmt.Errorf("bytes not equal: %s != %s", b1, b2))
}
<-time.After(time.Microsecond * 5)
}
c1.Close()
c2.Close()
cancel() // close the listener
wg.Done()
}
var cons = 5
var msgs = 50
log.Debugf("Running %d connections * %d msgs.\n", cons, msgs)
for i := 0; i < cons; i++ {
wg.Add(1)
go runPair(msgs)
}
log.Debugf("Waiting...\n")
wg.Wait()
// done!
<-time.After(time.Millisecond * 150)
if runtime.NumGoroutine() > 20 {
// panic("uncomment me to debug")
t.Fatal("leaking goroutines:", runtime.NumGoroutine())
}
}
package conn
import (
"fmt"
"strings"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
peer "github.com/jbenet/go-ipfs/p2p/peer"
debugerror "github.com/jbenet/go-ipfs/util/debugerror"
)
// String returns the string rep of d.
func (d *Dialer) String() string {
return fmt.Sprintf("<Dialer %s %s ...>", d.LocalPeer, d.LocalAddrs[0])
}
// Dial connects to a peer over a particular address
// Ensures raddr is part of peer.Addresses()
// Example: d.DialAddr(ctx, peer.Addresses()[0], peer)
func (d *Dialer) Dial(ctx context.Context, raddr ma.Multiaddr, remote peer.ID) (Conn, error) {
network, _, err := manet.DialArgs(raddr)
if err != nil {
return nil, err
}
if strings.HasPrefix(raddr.String(), "/ip4/0.0.0.0") {
return nil, debugerror.Errorf("Attempted to connect to zero address: %s", raddr)
}
var laddr ma.Multiaddr
if len(d.LocalAddrs) > 0 {
// laddr := MultiaddrNetMatch(raddr, d.LocalAddrs)
laddr = NetAddress(network, d.LocalAddrs)
if laddr == nil {
return nil, debugerror.Errorf("No local address for network %s", network)
}
}
// TODO: try to get reusing addr/ports to work.
// madialer := manet.Dialer{LocalAddr: laddr}
madialer := manet.Dialer{}
log.Debugf("%s dialing %s %s", d.LocalPeer, remote, raddr)
maconn, err := madialer.Dial(raddr)
if err != nil {
return nil, err
}
var connOut Conn
var errOut error
done := make(chan struct{})
// do it async to ensure we respect don contexteone
go func() {
defer func() { done <- struct{}{} }()
c, err := newSingleConn(ctx, d.LocalPeer, remote, maconn)
if err != nil {
errOut = err
return
}
if d.PrivateKey == nil {
log.Warning("dialer %s dialing INSECURELY %s at %s!", d, remote, raddr)
connOut = c
return
}
c2, err := newSecureConn(ctx, d.PrivateKey, c)
if err != nil {
errOut = err
c.Close()
return
}
connOut = c2
}()
select {
case <-ctx.Done():
maconn.Close()
return nil, ctx.Err()
case <-done:
// whew, finished.
}
return connOut, errOut
}
// MultiaddrProtocolsMatch returns whether two multiaddrs match in protocol stacks.
func MultiaddrProtocolsMatch(a, b ma.Multiaddr) bool {
ap := a.Protocols()
bp := b.Protocols()
if len(ap) != len(bp) {
return false
}
for i, api := range ap {
if api != bp[i] {
return false
}
}
return true
}
// MultiaddrNetMatch returns the first Multiaddr found to match network.
func MultiaddrNetMatch(tgt ma.Multiaddr, srcs []ma.Multiaddr) ma.Multiaddr {
for _, a := range srcs {
if MultiaddrProtocolsMatch(tgt, a) {
return a
}
}
return nil
}
// NetAddress returns the first Multiaddr found for a given network.
func NetAddress(n string, addrs []ma.Multiaddr) ma.Multiaddr {
for _, a := range addrs {
for _, p := range a.Protocols() {
if p.Name == n {
return a
}
}
}
return nil
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment