@cryptotaxi247 / kubo / commits / 735c3de7f

relay service -- streams across peers

this is the leadup into NAT traversal. note: doesn't work yet. hangs the test.

Juan Batiz-Benet committed Dec 24, 2014 at 11:16 UTC 735c3de7faab7b1b7f347850dfb74300655a560d
4 files changed +277 -5
net/ipfsnet/net.go
+8 -3
@@ -5,11 +5,13 @@ import (
5 "fmt"
6
7 ic "github.com/jbenet/go-ipfs/crypto"
8 + peer "github.com/jbenet/go-ipfs/peer"
9 +
10 inet "github.com/jbenet/go-ipfs/net"
11 ids "github.com/jbenet/go-ipfs/net/services/identify"
12 mux "github.com/jbenet/go-ipfs/net/services/mux"
13 + relay "github.com/jbenet/go-ipfs/net/services/relay"
14 swarm "github.com/jbenet/go-ipfs/net/swarm"
12 - peer "github.com/jbenet/go-ipfs/peer"
15
16 context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
17 ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup"
@@ -105,6 +107,7 @@ type Network struct {
107 swarm *swarm.Swarm // peer connection multiplexing
108 mux mux.Mux // protocol multiplexing
109 ids *ids.IDService
110 + relay *relay.RelayService
111
112 cg ctxgroup.ContextGroup // for Context closing
113 }
@@ -133,11 +136,13 @@ func NewNetwork(ctx context.Context, listen []ma.Multiaddr, local peer.ID,
136 n.mux.Handle((*stream)(s))
137 })
138
136 - // setup a conn handler that immediately "asks the other side about them"
137 - // this is ProtocolIdentify.
139 + // setup ProtocolIdentify to immediately "asks the other side about them"
140 n.ids = ids.NewIDService(n)
141 s.SetConnHandler(n.newConnHandler)
142
143 + // setup ProtocolRelay to allow traffic relaying.
144 + // Feed things we get for ourselves into the muxer.
145 + n.relay = relay.NewRelayService(n.cg.Context(), n, n.mux.HandleSync)
146 return n, nil
147 }
148
net/services/mux/mux.go
+9 -2
@@ -89,9 +89,16 @@ func (m *Mux) SetHandler(p inet.ProtocolID, h inet.StreamHandler) {
89 m.Unlock()
90 }
91
92 -// Handle reads the next name off the Stream, and calls a function
92 +// Handle reads the next name off the Stream, and calls a handler function
93 +// This is done in its own goroutine, to avoid blocking the caller.
94 func (m *Mux) Handle(s inet.Stream) {
95 + go m.HandleSync(s)
96 +}
97
98 +// HandleSync reads the next name off the Stream, and calls a handler function
99 +// This is done synchronously. The handler function will return before
100 +// HandleSync returns.
101 +func (m *Mux) HandleSync(s inet.Stream) {
102 ctx := context.Background()
103
104 name, handler, err := m.ReadProtocolHeader(s)
@@ -102,7 +109,7 @@ func (m *Mux) Handle(s inet.Stream) {
109 return
110 }
111
105 - log.Info("muxer handle protocol: %s", name)
112 + log.Infof("muxer handle protocol: %s", name)
113 log.Event(ctx, "muxHandle", eventlog.Metadata{"protocol": name})
114 handler(s)
115 }
net/services/relay/relay.go new
+159
@@ -0,0 +1,159 @@
1 +package relay
2 +
3 +import (
4 + "fmt"
5 + "io"
6 +
7 + context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
8 + ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup"
9 + mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
10 +
11 + inet "github.com/jbenet/go-ipfs/net"
12 + peer "github.com/jbenet/go-ipfs/peer"
13 + eventlog "github.com/jbenet/go-ipfs/util/eventlog"
14 +)
15 +
16 +var log = eventlog.Logger("relay")
17 +
18 +// ProtocolRelay is the ProtocolID of the Relay Service.
19 +const ProtocolRelay inet.ProtocolID = "/ipfs/relay"
20 +
21 +// Relay is a structure that implements ProtocolRelay.
22 +// It is a simple relay service which forwards traffic
23 +// between two directly connected peers.
24 +//
25 +// the protocol is very simple:
26 +//
27 +// /ipfs/relay\n
28 +// <multihash src id>
29 +// <multihash dst id>
30 +// <data stream>
31 +//
32 +type RelayService struct {
33 + Network inet.Network
34 + handler inet.StreamHandler // for streams sent to us locally.
35 +
36 + cg ctxgroup.ContextGroup
37 +}
38 +
39 +func NewRelayService(ctx context.Context, n inet.Network, sh inet.StreamHandler) *RelayService {
40 + s := &RelayService{
41 + Network: n,
42 + handler: sh,
43 + cg: ctxgroup.WithContext(ctx),
44 + }
45 + n.SetHandler(inet.ProtocolRelay, s.requestHandler)
46 + return s
47 +}
48 +
49 +// requestHandler is the function called by clients
50 +func (rs *RelayService) requestHandler(s inet.Stream) {
51 + if err := rs.handleStream(s); err != nil {
52 + log.Error("RelayService error:", err)
53 + }
54 +}
55 +
56 +// handleStream is our own handler, which returns an error for simplicity.
57 +func (rs *RelayService) handleStream(s inet.Stream) error {
58 + defer s.Close()
59 +
60 + // read the header (src and dst peer.IDs)
61 + src, dst, err := ReadHeader(s)
62 + if err != nil {
63 + return fmt.Errorf("stream with bad header: %s", err)
64 + }
65 +
66 + local := rs.Network.LocalPeer()
67 +
68 + switch {
69 + case src == local:
70 + return fmt.Errorf("relaying from self")
71 + case dst == local: // it's for us! yaaay.
72 + log.Debugf("%s consuming stream from %s", rs.Network.LocalPeer(), src)
73 + return rs.consumeStream(s)
74 + default: // src and dst are not local. relay it.
75 + log.Debugf("%s relaying stream %s <--> %s", rs.Network.LocalPeer(), src, dst)
76 + return rs.pipeStream(src, dst, s)
77 + }
78 +}
79 +
80 +// consumeStream connects streams directed to the local peer
81 +// to our handler, with the header now stripped (read).
82 +func (rs *RelayService) consumeStream(s inet.Stream) error {
83 + rs.handler(s) // boom.
84 + return nil
85 +}
86 +
87 +// pipeStream relays over a stream to a remote peer. It's like `cat`
88 +func (rs *RelayService) pipeStream(src, dst peer.ID, s inet.Stream) error {
89 + s2, err := rs.openStreamToPeer(dst)
90 + if err != nil {
91 + return fmt.Errorf("failed to open stream to peer: %s -- %s", dst, err)
92 + }
93 +
94 + if err := WriteHeader(s2, src, dst); err != nil {
95 + return err
96 + }
97 +
98 + // connect the series of tubes.
99 + done := make(chan retio, 2)
100 + go func() {
101 + n, err := io.Copy(s2, s)
102 + done <- retio{n, err}
103 + }()
104 + go func() {
105 + n, err := io.Copy(s, s2)
106 + done <- retio{n, err}
107 + }()
108 +
109 + r1 := <-done
110 + r2 := <-done
111 + log.Infof("relayed %d/%d bytes between %s and %s", r1.n, r2.n, src, dst)
112 +
113 + if r1.err != nil {
114 + return r1.err
115 + }
116 + return r2.err
117 +}
118 +
119 +// openStreamToPeer opens a pipe to a remote endpoint
120 +// for now, can only open streams to directly connected peers.
121 +// maybe we can do some routing later on.
122 +func (rs *RelayService) openStreamToPeer(p peer.ID) (inet.Stream, error) {
123 + return rs.Network.NewStream(ProtocolRelay, p)
124 +}
125 +
126 +func ReadHeader(r io.Reader) (src, dst peer.ID, err error) {
127 +
128 + mhr := mh.NewReader(r)
129 +
130 + s, err := mhr.ReadMultihash()
131 + if err != nil {
132 + return "", "", err
133 + }
134 +
135 + d, err := mhr.ReadMultihash()
136 + if err != nil {
137 + return "", "", err
138 + }
139 +
140 + return peer.ID(s), peer.ID(d), nil
141 +}
142 +
143 +func WriteHeader(w io.Writer, src, dst peer.ID) error {
144 + // write header to w.
145 + mhw := mh.NewWriter(w)
146 + if err := mhw.WriteMultihash(mh.Multihash(src)); err != nil {
147 + return fmt.Errorf("failed to write relay header: %s -- %s", dst, err)
148 + }
149 + if err := mhw.WriteMultihash(mh.Multihash(dst)); err != nil {
150 + return fmt.Errorf("failed to write relay header: %s -- %s", dst, err)
151 + }
152 +
153 + return nil
154 +}
155 +
156 +type retio struct {
157 + n int64
158 + err error
159 +}
net/services/relay/relay_test.go new
+101
@@ -0,0 +1,101 @@
1 +package relay_test
2 +
3 +import (
4 + "io"
5 + "testing"
6 +
7 + inet "github.com/jbenet/go-ipfs/net"
8 + netutil "github.com/jbenet/go-ipfs/net/ipfsnet/util"
9 + mux "github.com/jbenet/go-ipfs/net/services/mux"
10 + relay "github.com/jbenet/go-ipfs/net/services/relay"
11 + eventlog "github.com/jbenet/go-ipfs/util/eventlog"
12 +
13 + context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
14 +)
15 +
16 +var log = eventlog.Logger("relay_test")
17 +
18 +func TestRelaySimple(t *testing.T) {
19 +
20 + ctx := context.Background()
21 +
22 + // these networks have the relay service wired in already.
23 + n1 := netutil.GenNetwork(t, ctx)
24 + n2 := netutil.GenNetwork(t, ctx)
25 + n3 := netutil.GenNetwork(t, ctx)
26 +
27 + n1p := n1.LocalPeer()
28 + n2p := n2.LocalPeer()
29 + n3p := n3.LocalPeer()
30 +
31 + netutil.DivulgeAddresses(n2, n1)
32 + netutil.DivulgeAddresses(n2, n3)
33 +
34 + if err := n1.DialPeer(ctx, n2p); err != nil {
35 + t.Fatalf("Failed to dial:", err)
36 + }
37 + if err := n3.DialPeer(ctx, n2p); err != nil {
38 + t.Fatalf("Failed to dial:", err)
39 + }
40 +
41 + // setup handler on n3 to copy everything over to the pipe.
42 + piper, pipew := io.Pipe()
43 + n3.SetHandler(inet.ProtocolTesting, func(s inet.Stream) {
44 + log.Debug("relay stream opened to n3!")
45 + log.Debug("piping and echoing everything")
46 + w := io.MultiWriter(s, pipew)
47 + io.Copy(w, s)
48 + log.Debug("closing stream")
49 + s.Close()
50 + })
51 +
52 + // ok, now we can try to relay n1--->n2--->n3.
53 + log.Debug("open relay stream")
54 + s, err := n1.NewStream(relay.ProtocolRelay, n2p)
55 + if err != nil {
56 + t.Fatal(err)
57 + }
58 +
59 + // ok first thing we write the relay header n1->n3
60 + log.Debug("write relay header")
61 + if err := relay.WriteHeader(s, n1p, n3p); err != nil {
62 + t.Fatal(err)
63 + }
64 +
65 + // ok now the header's there, we can write the next protocol header.
66 + log.Debug("write testing header")
67 + if err := mux.WriteProtocolHeader(inet.ProtocolTesting, s); err != nil {
68 + t.Fatal(err)
69 + }
70 +
71 + // okay, now we should be able to write text, and read it out.
72 + buf1 := []byte("abcdefghij")
73 + buf2 := make([]byte, 10)
74 + buf3 := make([]byte, 10)
75 + log.Debug("write in some text.")
76 + if _, err := s.Write(buf1); err != nil {
77 + t.Fatal(err)
78 + }
79 +
80 + // read it out from the pipe.
81 + log.Debug("read it out from the pipe.")
82 + if _, err := io.ReadFull(piper, buf2); err != nil {
83 + t.Fatal(err)
84 + }
85 + if string(buf1) != string(buf2) {
86 + t.Fatal("should've gotten that text out of the pipe")
87 + }
88 +
89 + // read it out from the stream (echoed)
90 + log.Debug("read it out from the stream (echoed).")
91 + if _, err := io.ReadFull(s, buf3); err != nil {
92 + t.Fatal(err)
93 + }
94 + if string(buf1) != string(buf3) {
95 + t.Fatal("should've gotten that text out of the stream")
96 + }
97 +
98 + // sweet. relay works.
99 + log.Debug("sweet, relay works.")
100 + s.Close()
101 +}