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
+}