From f928ecce2fde6eae83d2e30f9e589e86c4cc8cab Mon Sep 17 00:00:00 2001 From: Jeromy Date: Mon, 16 May 2016 12:29:11 -0700 Subject: [PATCH 01/93] first working commit --- p2p/transport/websocket/websocket.go | 214 +++++++++++++++++++++++++++ p2p/transport/websocket/ws_test.go | 65 ++++++++ 2 files changed, 279 insertions(+) create mode 100644 p2p/transport/websocket/websocket.go create mode 100644 p2p/transport/websocket/ws_test.go diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go new file mode 100644 index 0000000000..ba59fc290d --- /dev/null +++ b/p2p/transport/websocket/websocket.go @@ -0,0 +1,214 @@ +package websocket + +import ( + "fmt" + "log" + "net" + "net/http" + "net/url" + + tpt "github.com/ipfs/go-libp2p-transport" + manet "github.com/jbenet/go-multiaddr-net" + mafmt "github.com/whyrusleeping/mafmt" + ws "golang.org/x/net/websocket" + + ma "github.com/jbenet/go-multiaddr" + "golang.org/x/net/context" +) + +var WsProtocol = ma.Protocol{ + Code: 477, + Name: "ws", + VCode: ma.CodeToVarint(477), +} + +var WsFmt = mafmt.And(mafmt.TCP, mafmt.Base(WsProtocol.Code)) + +var WsCodec = &manet.NetCodec{ + NetAddrNetworks: []string{"websocket"}, + ProtocolName: "ws", + ConvertMultiaddr: ConvertWebsocketMultiaddrToNetAddr, + ParseNetAddr: ParseWebsocketNetAddr, +} + +func init() { + err := ma.AddProtocol(WsProtocol) + if err != nil { + log.Fatalf("error registering websocket protocol: %s", err) + } + + manet.RegisterNetCodec(WsCodec) +} + +func ConvertWebsocketMultiaddrToNetAddr(maddr ma.Multiaddr) (net.Addr, error) { + panic("not yet") +} + +func ParseWebsocketNetAddr(a net.Addr) (ma.Multiaddr, error) { + wsa, ok := a.(*ws.Addr) + if !ok { + return nil, fmt.Errorf("not a websocket address") + } + + tcpaddr, err := net.ResolveTCPAddr("tcp", wsa.Host) + if err != nil { + return nil, err + } + + tcpma, err := manet.FromNetAddr(tcpaddr) + if err != nil { + return nil, err + } + + wsma, err := ma.NewMultiaddr("/ws") + if err != nil { + return nil, err + } + + OUT := tcpma.Encapsulate(wsma) + return OUT, nil +} + +type WebsocketTransport struct{} + +func (t *WebsocketTransport) Matches(a ma.Multiaddr) bool { + return WsFmt.Matches(a) +} + +func (t *WebsocketTransport) Dialer(_ ma.Multiaddr, opts ...tpt.DialOpt) (tpt.Dialer, error) { + return &dialer{}, nil +} + +type dialer struct{} + +func parseMultiaddr(a ma.Multiaddr) (string, error) { + _, host, err := manet.DialArgs(a) + if err != nil { + return "", err + } + + return "ws://" + host, nil +} + +func (d *dialer) Dial(raddr ma.Multiaddr) (tpt.Conn, error) { + wsurl, err := parseMultiaddr(raddr) + if err != nil { + log.Println("parse multiaddr failed: ", err) + return nil, err + } + + wscon, err := ws.Dial(wsurl, "", "http://127.0.0.1:0/") + if err != nil { + return nil, err + } + + mnc, err := manet.WrapNetConn(wscon) + if err != nil { + return nil, err + } + + return &wsConn{ + Conn: mnc, + }, nil +} + +func (d *dialer) Matches(a ma.Multiaddr) bool { + return WsFmt.Matches(a) +} + +type wsConn struct { + manet.Conn + t tpt.Transport +} + +func (c *wsConn) Transport() tpt.Transport { + return c.t +} + +type listener struct { + manet.Listener + + incoming chan *conn + + tpt tpt.Transport +} + +type conn struct { + *ws.Conn + + done func() +} + +func (c *conn) Close() error { + c.done() + return c.Conn.Close() +} + +func (t *WebsocketTransport) Listen(a ma.Multiaddr) (tpt.Listener, error) { + list, err := manet.Listen(a) + if err != nil { + return nil, err + } + + tlist := t.wrapListener(list) + + u, err := url.Parse("ws://" + list.Addr().String()) + if err != nil { + return nil, err + } + + s := &ws.Server{ + Handler: tlist.handleWsConn, + Config: ws.Config{Origin: u}, + } + + go http.Serve(list.NetListener(), s) + + return tlist, nil +} + +func (t *WebsocketTransport) wrapListener(l manet.Listener) *listener { + return &listener{ + Listener: l, + incoming: make(chan *conn), + tpt: t, + } +} + +func (l *listener) handleWsConn(s *ws.Conn) { + ctx, cancel := context.WithCancel(context.Background()) + + l.incoming <- &conn{ + Conn: s, + done: cancel, + } + + // wait until conn gets closed, otherwise the handler closes it early + <-ctx.Done() +} + +func (l *listener) Accept() (tpt.Conn, error) { + c, ok := <-l.incoming + if !ok { + return nil, fmt.Errorf("listener is closed") + } + + mnc, err := manet.WrapNetConn(c) + if err != nil { + return nil, err + } + + return &wsConn{ + Conn: mnc, + t: l.tpt, + }, nil +} + +func (l *listener) Multiaddr() ma.Multiaddr { + wsma, err := ma.NewMultiaddr("/ws") + if err != nil { + panic(err) + } + + return l.Listener.Multiaddr().Encapsulate(wsma) +} diff --git a/p2p/transport/websocket/ws_test.go b/p2p/transport/websocket/ws_test.go new file mode 100644 index 0000000000..5bda992d17 --- /dev/null +++ b/p2p/transport/websocket/ws_test.go @@ -0,0 +1,65 @@ +package websocket + +import ( + "log" + "testing" + "time" + + ma "github.com/jbenet/go-multiaddr" +) + +var _ = log.Println + +func TestMultiaddrParsing(t *testing.T) { + addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") + if err != nil { + t.Fatal(err) + } + + _, err = parseMultiaddr(addr) + if err != nil { + t.Fatal(err) + } +} + +func TestWebsocketListen(t *testing.T) { + zero, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0/ws") + if err != nil { + t.Fatal(err) + } + + tpt := &WebsocketTransport{} + l, err := tpt.Listen(zero) + if err != nil { + t.Fatal(err) + } + + go func() { + d, _ := tpt.Dialer(nil) + c, err := d.Dial(l.Multiaddr()) + if err != nil { + t.Error(err) + return + } + + c.Write([]byte("HELLO WORLD!")) + time.Sleep(time.Second) + c.Close() + }() + + c, err := l.Accept() + if err != nil { + t.Fatal(err) + } + + buf := make([]byte, 32) + n, err := c.Read(buf) + if err != nil { + t.Fatal(err) + } + + log.Printf("READ: %s", buf[:n]) + c.Close() + l.Close() + time.Sleep(time.Second) +} From 83015ba5dcda52b6e4b265ecd422b587ea97445f Mon Sep 17 00:00:00 2001 From: Jeromy Date: Mon, 16 May 2016 15:17:21 -0700 Subject: [PATCH 02/93] complete parsers --- p2p/transport/websocket/websocket.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index ba59fc290d..e37cb98a85 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -41,7 +41,17 @@ func init() { } func ConvertWebsocketMultiaddrToNetAddr(maddr ma.Multiaddr) (net.Addr, error) { - panic("not yet") + _, host, err := manet.DialArgs(maddr) + if err != nil { + return nil, err + } + + a := &ws.Addr{ + URL: &url.URL{ + Host: host, + }, + } + return a, nil } func ParseWebsocketNetAddr(a net.Addr) (ma.Multiaddr, error) { @@ -65,8 +75,7 @@ func ParseWebsocketNetAddr(a net.Addr) (ma.Multiaddr, error) { return nil, err } - OUT := tcpma.Encapsulate(wsma) - return OUT, nil + return tcpma.Encapsulate(wsma), nil } type WebsocketTransport struct{} From 5d09bed16b773696d277ee3908587bbd5e93b127 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Tue, 23 Aug 2016 17:31:26 -0700 Subject: [PATCH 03/93] gx publish version 1.0.2 --- p2p/transport/websocket/websocket.go | 8 ++++---- p2p/transport/websocket/ws_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index e37cb98a85..fef3673ddd 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -7,13 +7,13 @@ import ( "net/http" "net/url" - tpt "github.com/ipfs/go-libp2p-transport" - manet "github.com/jbenet/go-multiaddr-net" - mafmt "github.com/whyrusleeping/mafmt" ws "golang.org/x/net/websocket" + manet "gx/ipfs/QmPpRcbNUXauP3zWZ1NJMLWpe4QnmEHrd2ba2D3yqWznw7/go-multiaddr-net" + tpt "gx/ipfs/QmbRuJ16EfPGWuEguoNxwPacpeHFUAZb2XQXVvUt5Vxg5q/go-libp2p-transport" + mafmt "gx/ipfs/QmeLQ13LftT9XhNn22piZc3GP56fGqhijuL5Y8KdUaRn1g/mafmt" - ma "github.com/jbenet/go-multiaddr" "golang.org/x/net/context" + ma "gx/ipfs/QmYzDkkgAEmrcNzFCiYo6L1dTX4EAG1gZkbtdbd9trL4vd/go-multiaddr" ) var WsProtocol = ma.Protocol{ diff --git a/p2p/transport/websocket/ws_test.go b/p2p/transport/websocket/ws_test.go index 5bda992d17..9a75c9437d 100644 --- a/p2p/transport/websocket/ws_test.go +++ b/p2p/transport/websocket/ws_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - ma "github.com/jbenet/go-multiaddr" + ma "gx/ipfs/QmYzDkkgAEmrcNzFCiYo6L1dTX4EAG1gZkbtdbd9trL4vd/go-multiaddr" ) var _ = log.Println From 6cb2231c24118a34aa8b5b6fbfff11ff2a33c9a9 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Tue, 23 Aug 2016 19:21:43 -0700 Subject: [PATCH 04/93] gx publish version 1.0.3 --- p2p/transport/websocket/websocket.go | 10 ++++++---- p2p/transport/websocket/ws_test.go | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index fef3673ddd..bdae6a37c5 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -7,13 +7,13 @@ import ( "net/http" "net/url" + tpt "github.com/ipfs/go-libp2p-transport" + manet "github.com/jbenet/go-multiaddr-net" + mafmt "github.com/whyrusleeping/mafmt" ws "golang.org/x/net/websocket" - manet "gx/ipfs/QmPpRcbNUXauP3zWZ1NJMLWpe4QnmEHrd2ba2D3yqWznw7/go-multiaddr-net" - tpt "gx/ipfs/QmbRuJ16EfPGWuEguoNxwPacpeHFUAZb2XQXVvUt5Vxg5q/go-libp2p-transport" - mafmt "gx/ipfs/QmeLQ13LftT9XhNn22piZc3GP56fGqhijuL5Y8KdUaRn1g/mafmt" + ma "github.com/jbenet/go-multiaddr" "golang.org/x/net/context" - ma "gx/ipfs/QmYzDkkgAEmrcNzFCiYo6L1dTX4EAG1gZkbtdbd9trL4vd/go-multiaddr" ) var WsProtocol = ma.Protocol{ @@ -221,3 +221,5 @@ func (l *listener) Multiaddr() ma.Multiaddr { return l.Listener.Multiaddr().Encapsulate(wsma) } + +var _ tpt.Transport = (*WebsocketTransport)(nil) diff --git a/p2p/transport/websocket/ws_test.go b/p2p/transport/websocket/ws_test.go index 9a75c9437d..5bda992d17 100644 --- a/p2p/transport/websocket/ws_test.go +++ b/p2p/transport/websocket/ws_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - ma "gx/ipfs/QmYzDkkgAEmrcNzFCiYo6L1dTX4EAG1gZkbtdbd9trL4vd/go-multiaddr" + ma "github.com/jbenet/go-multiaddr" ) var _ = log.Println From 10b6a10746ffd7618b8159f7fd54fcf7eff1cea5 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Tue, 23 Aug 2016 22:04:22 -0700 Subject: [PATCH 05/93] some cleanup --- p2p/transport/websocket/websocket.go | 4 +--- p2p/transport/websocket/ws_test.go | 19 +++++++++---------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index bdae6a37c5..bd66b99d82 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -2,7 +2,6 @@ package websocket import ( "fmt" - "log" "net" "net/http" "net/url" @@ -34,7 +33,7 @@ var WsCodec = &manet.NetCodec{ func init() { err := ma.AddProtocol(WsProtocol) if err != nil { - log.Fatalf("error registering websocket protocol: %s", err) + panic(fmt.Errorf("error registering websocket protocol: %s", err)) } manet.RegisterNetCodec(WsCodec) @@ -102,7 +101,6 @@ func parseMultiaddr(a ma.Multiaddr) (string, error) { func (d *dialer) Dial(raddr ma.Multiaddr) (tpt.Conn, error) { wsurl, err := parseMultiaddr(raddr) if err != nil { - log.Println("parse multiaddr failed: ", err) return nil, err } diff --git a/p2p/transport/websocket/ws_test.go b/p2p/transport/websocket/ws_test.go index 5bda992d17..678a4cbeb9 100644 --- a/p2p/transport/websocket/ws_test.go +++ b/p2p/transport/websocket/ws_test.go @@ -1,15 +1,12 @@ package websocket import ( - "log" + "bytes" "testing" - "time" ma "github.com/jbenet/go-multiaddr" ) -var _ = log.Println - func TestMultiaddrParsing(t *testing.T) { addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") if err != nil { @@ -33,6 +30,9 @@ func TestWebsocketListen(t *testing.T) { if err != nil { t.Fatal(err) } + defer l.Close() + + msg := []byte("HELLO WORLD") go func() { d, _ := tpt.Dialer(nil) @@ -42,8 +42,7 @@ func TestWebsocketListen(t *testing.T) { return } - c.Write([]byte("HELLO WORLD!")) - time.Sleep(time.Second) + c.Write(msg) c.Close() }() @@ -51,6 +50,7 @@ func TestWebsocketListen(t *testing.T) { if err != nil { t.Fatal(err) } + defer c.Close() buf := make([]byte, 32) n, err := c.Read(buf) @@ -58,8 +58,7 @@ func TestWebsocketListen(t *testing.T) { t.Fatal(err) } - log.Printf("READ: %s", buf[:n]) - c.Close() - l.Close() - time.Sleep(time.Second) + if !bytes.Equal(buf[:n], msg) { + t.Fatal("got wrong message", buf[:n], msg) + } } From f87a8406e9f14d606e739362853f6b7b3126e7ec Mon Sep 17 00:00:00 2001 From: Jeromy Date: Wed, 24 Aug 2016 09:27:17 -0700 Subject: [PATCH 06/93] gx publish version 1.0.5, use builtin context --- p2p/transport/websocket/websocket.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index bd66b99d82..f048c006cb 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -1,6 +1,7 @@ package websocket import ( + "context" "fmt" "net" "net/http" @@ -12,7 +13,6 @@ import ( ws "golang.org/x/net/websocket" ma "github.com/jbenet/go-multiaddr" - "golang.org/x/net/context" ) var WsProtocol = ma.Protocol{ From 598f265e6f53fe6336c59e75c3050ae5838f527a Mon Sep 17 00:00:00 2001 From: Friedel Ziegelmayer Date: Thu, 15 Sep 2016 10:06:05 +0200 Subject: [PATCH 07/93] fix: use binary frames instead of text frames This fixes websocket interop with js-ipfs --- p2p/transport/websocket/websocket.go | 1 + 1 file changed, 1 insertion(+) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index f048c006cb..fed6bc3655 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -184,6 +184,7 @@ func (t *WebsocketTransport) wrapListener(l manet.Listener) *listener { func (l *listener) handleWsConn(s *ws.Conn) { ctx, cancel := context.WithCancel(context.Background()) + s.PayloadType = ws.BinaryFrame l.incoming <- &conn{ Conn: s, From b80c12878c795bf51a9f2b57aa9fba02d198975b Mon Sep 17 00:00:00 2001 From: Jeromy Date: Thu, 15 Sep 2016 22:24:15 -0700 Subject: [PATCH 08/93] gx publish v1.2.0 --- p2p/transport/websocket/websocket.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index fed6bc3655..331f1d2130 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -99,6 +99,10 @@ func parseMultiaddr(a ma.Multiaddr) (string, error) { } func (d *dialer) Dial(raddr ma.Multiaddr) (tpt.Conn, error) { + return d.DialContext(context.Background(), raddr) +} + +func (d *dialer) DialContext(ctx context.Context, raddr ma.Multiaddr) (tpt.Conn, error) { wsurl, err := parseMultiaddr(raddr) if err != nil { return nil, err From f5f0af8297d64a4daab4b21d74a0620eface355b Mon Sep 17 00:00:00 2001 From: Jeromy Date: Sun, 25 Sep 2016 01:29:29 -0700 Subject: [PATCH 09/93] update libp2p-transport --- p2p/transport/websocket/websocket.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 331f1d2130..ee8678a2cc 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -7,8 +7,8 @@ import ( "net/http" "net/url" - tpt "github.com/ipfs/go-libp2p-transport" manet "github.com/jbenet/go-multiaddr-net" + tpt "github.com/libp2p/go-libp2p-transport" mafmt "github.com/whyrusleeping/mafmt" ws "golang.org/x/net/websocket" From fa8d0d27d1832e727926dc0789ba4c484e060cd1 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Tue, 4 Oct 2016 19:24:36 -0700 Subject: [PATCH 10/93] gx publish 1.4.0 --- p2p/transport/websocket/websocket.go | 5 ++--- p2p/transport/websocket/ws_test.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index ee8678a2cc..69fc11c408 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -7,12 +7,11 @@ import ( "net/http" "net/url" - manet "github.com/jbenet/go-multiaddr-net" tpt "github.com/libp2p/go-libp2p-transport" + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" mafmt "github.com/whyrusleeping/mafmt" ws "golang.org/x/net/websocket" - - ma "github.com/jbenet/go-multiaddr" ) var WsProtocol = ma.Protocol{ diff --git a/p2p/transport/websocket/ws_test.go b/p2p/transport/websocket/ws_test.go index 678a4cbeb9..1667ac42b8 100644 --- a/p2p/transport/websocket/ws_test.go +++ b/p2p/transport/websocket/ws_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - ma "github.com/jbenet/go-multiaddr" + ma "github.com/multiformats/go-multiaddr" ) func TestMultiaddrParsing(t *testing.T) { From 398fcd59b4ca90d36a72b7e4714f16b795f07032 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 1 Jul 2017 23:11:20 +0200 Subject: [PATCH 11/93] feat: upgrade to use gorilla/websocket Closes #12 --- p2p/transport/websocket/websocket.go | 106 ++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 17 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 69fc11c408..46130c150c 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -6,7 +6,9 @@ import ( "net" "net/http" "net/url" + "time" + wsGorilla "github.com/gorilla/websocket" tpt "github.com/libp2p/go-libp2p-transport" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" @@ -29,6 +31,9 @@ var WsCodec = &manet.NetCodec{ ParseNetAddr: ParseWebsocketNetAddr, } +// Default gorilla upgrader +var upgrader = wsGorilla.Upgrader{} + func init() { err := ma.AddProtocol(WsProtocol) if err != nil { @@ -107,12 +112,15 @@ func (d *dialer) DialContext(ctx context.Context, raddr ma.Multiaddr) (tpt.Conn, return nil, err } - wscon, err := ws.Dial(wsurl, "", "http://127.0.0.1:0/") + // TODO: figure out origins, probably don't work for us + // header := http.Header{} + // header.Set("Origin", "http://127.0.0.1:0/") + wscon, _, err := wsGorilla.DefaultDialer.Dial(wsurl, nil) if err != nil { return nil, err } - mnc, err := manet.WrapNetConn(wscon) + mnc, err := manet.WrapNetConn(NewGorillaNetConn(wscon)) if err != nil { return nil, err } @@ -141,17 +149,19 @@ type listener struct { incoming chan *conn tpt tpt.Transport + + origin *url.URL } type conn struct { - *ws.Conn + *GorillaNetConn done func() } func (c *conn) Close() error { c.done() - return c.Conn.Close() + return c.GorillaNetConn.Close() } func (t *WebsocketTransport) Listen(a ma.Multiaddr) (tpt.Listener, error) { @@ -160,38 +170,41 @@ func (t *WebsocketTransport) Listen(a ma.Multiaddr) (tpt.Listener, error) { return nil, err } - tlist := t.wrapListener(list) - - u, err := url.Parse("ws://" + list.Addr().String()) + u, err := url.Parse("http://" + list.Addr().String()) if err != nil { return nil, err } - s := &ws.Server{ - Handler: tlist.handleWsConn, - Config: ws.Config{Origin: u}, - } + tlist := t.wrapListener(list, u) - go http.Serve(list.NetListener(), s) + http.HandleFunc("/", tlist.handleWsConn) + go http.Serve(list.NetListener(), nil) return tlist, nil } -func (t *WebsocketTransport) wrapListener(l manet.Listener) *listener { +func (t *WebsocketTransport) wrapListener(l manet.Listener, origin *url.URL) *listener { return &listener{ Listener: l, incoming: make(chan *conn), tpt: t, + origin: origin, } } -func (l *listener) handleWsConn(s *ws.Conn) { +func (l *listener) handleWsConn(w http.ResponseWriter, r *http.Request) { + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + http.Error(w, "Failed to upgrade websocket", 400) + return + } + ctx, cancel := context.WithCancel(context.Background()) - s.PayloadType = ws.BinaryFrame + wrapped := NewGorillaNetConn(c) l.incoming <- &conn{ - Conn: s, - done: cancel, + GorillaNetConn: &wrapped, + done: cancel, } // wait until conn gets closed, otherwise the handler closes it early @@ -225,3 +238,62 @@ func (l *listener) Multiaddr() ma.Multiaddr { } var _ tpt.Transport = (*WebsocketTransport)(nil) + +type GorillaNetConn struct { + Inner *wsGorilla.Conn + DefaultMessageType int +} + +func (c GorillaNetConn) Read(b []byte) (n int, err error) { + fmt.Println("reading") + _, r, err := c.Inner.NextReader() + if err != nil { + return 0, err + } + + return r.Read(b) +} + +func (c GorillaNetConn) Write(b []byte) (n int, err error) { + fmt.Printf("write %s\n", string(b)) + if err := c.Inner.WriteMessage(c.DefaultMessageType, b); err != nil { + return 0, err + } + + return len(b), nil +} + +func (c GorillaNetConn) Close() error { + return c.Inner.Close() +} + +func (c GorillaNetConn) LocalAddr() net.Addr { + return c.Inner.LocalAddr() +} + +func (c GorillaNetConn) RemoteAddr() net.Addr { + return c.Inner.RemoteAddr() +} + +func (c GorillaNetConn) SetDeadline(t time.Time) error { + if err := c.SetReadDeadline(t); err != nil { + return err + } + + return c.SetReadDeadline(t) +} + +func (c GorillaNetConn) SetReadDeadline(t time.Time) error { + return c.Inner.SetReadDeadline(t) +} + +func (c GorillaNetConn) SetWriteDeadline(t time.Time) error { + return c.Inner.SetWriteDeadline(t) +} + +func NewGorillaNetConn(raw *wsGorilla.Conn) GorillaNetConn { + return GorillaNetConn{ + Inner: raw, + DefaultMessageType: wsGorilla.BinaryMessage, + } +} From 0cb827d19b2bbed0ee6bac0b739f2a757ee3d72b Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 4 Jul 2017 22:39:10 +0200 Subject: [PATCH 12/93] gxified gorilla/websocket --- p2p/transport/websocket/websocket.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 46130c150c..20ba9cd479 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -8,12 +8,12 @@ import ( "net/url" "time" - wsGorilla "github.com/gorilla/websocket" tpt "github.com/libp2p/go-libp2p-transport" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" mafmt "github.com/whyrusleeping/mafmt" ws "golang.org/x/net/websocket" + wsGorilla "gx/ipfs/QmdKzkTPWmQ4nc8McYVb9TJYzRGmux9sqySQBD4nhbjQpf/." ) var WsProtocol = ma.Protocol{ From 32c4c20ddd78b3fdf3b423872c68270aa9120a41 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 4 Jul 2017 22:47:16 +0200 Subject: [PATCH 13/93] first round of cr fixes --- p2p/transport/websocket/websocket.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 20ba9cd479..d0855be534 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -13,7 +13,7 @@ import ( manet "github.com/multiformats/go-multiaddr-net" mafmt "github.com/whyrusleeping/mafmt" ws "golang.org/x/net/websocket" - wsGorilla "gx/ipfs/QmdKzkTPWmQ4nc8McYVb9TJYzRGmux9sqySQBD4nhbjQpf/." + wsGorilla "gx/ipfs/QmdKzkTPWmQ4nc8McYVb9TJYzRGmux9sqySQBD4nhbjQpf" ) var WsProtocol = ma.Protocol{ @@ -245,7 +245,6 @@ type GorillaNetConn struct { } func (c GorillaNetConn) Read(b []byte) (n int, err error) { - fmt.Println("reading") _, r, err := c.Inner.NextReader() if err != nil { return 0, err @@ -255,7 +254,6 @@ func (c GorillaNetConn) Read(b []byte) (n int, err error) { } func (c GorillaNetConn) Write(b []byte) (n int, err error) { - fmt.Printf("write %s\n", string(b)) if err := c.Inner.WriteMessage(c.DefaultMessageType, b); err != nil { return 0, err } @@ -280,7 +278,7 @@ func (c GorillaNetConn) SetDeadline(t time.Time) error { return err } - return c.SetReadDeadline(t) + return c.SetWriteDeadline(t) } func (c GorillaNetConn) SetReadDeadline(t time.Time) error { From f5d0996f7a73d22dda07128cf482231b7bfe222a Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 4 Jul 2017 22:50:03 +0200 Subject: [PATCH 14/93] more cr fixes --- p2p/transport/websocket/websocket.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index d0855be534..b0dac81341 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -177,8 +177,7 @@ func (t *WebsocketTransport) Listen(a ma.Multiaddr) (tpt.Listener, error) { tlist := t.wrapListener(list, u) - http.HandleFunc("/", tlist.handleWsConn) - go http.Serve(list.NetListener(), nil) + go http.Serve(list.NetListener(), tlist) return tlist, nil } @@ -192,7 +191,7 @@ func (t *WebsocketTransport) wrapListener(l manet.Listener, origin *url.URL) *li } } -func (l *listener) handleWsConn(w http.ResponseWriter, r *http.Request) { +func (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { c, err := upgrader.Upgrade(w, r, nil) if err != nil { http.Error(w, "Failed to upgrade websocket", 400) From 661ec220fd14f169bbcea42aad17779d1d441520 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 5 Jul 2017 17:25:15 +0200 Subject: [PATCH 15/93] use more pointers --- p2p/transport/websocket/websocket.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index b0dac81341..4caffb5936 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -202,7 +202,7 @@ func (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { wrapped := NewGorillaNetConn(c) l.incoming <- &conn{ - GorillaNetConn: &wrapped, + GorillaNetConn: wrapped, done: cancel, } @@ -243,7 +243,7 @@ type GorillaNetConn struct { DefaultMessageType int } -func (c GorillaNetConn) Read(b []byte) (n int, err error) { +func (c *GorillaNetConn) Read(b []byte) (n int, err error) { _, r, err := c.Inner.NextReader() if err != nil { return 0, err @@ -252,7 +252,7 @@ func (c GorillaNetConn) Read(b []byte) (n int, err error) { return r.Read(b) } -func (c GorillaNetConn) Write(b []byte) (n int, err error) { +func (c *GorillaNetConn) Write(b []byte) (n int, err error) { if err := c.Inner.WriteMessage(c.DefaultMessageType, b); err != nil { return 0, err } @@ -260,19 +260,19 @@ func (c GorillaNetConn) Write(b []byte) (n int, err error) { return len(b), nil } -func (c GorillaNetConn) Close() error { +func (c *GorillaNetConn) Close() error { return c.Inner.Close() } -func (c GorillaNetConn) LocalAddr() net.Addr { +func (c *GorillaNetConn) LocalAddr() net.Addr { return c.Inner.LocalAddr() } -func (c GorillaNetConn) RemoteAddr() net.Addr { +func (c *GorillaNetConn) RemoteAddr() net.Addr { return c.Inner.RemoteAddr() } -func (c GorillaNetConn) SetDeadline(t time.Time) error { +func (c *GorillaNetConn) SetDeadline(t time.Time) error { if err := c.SetReadDeadline(t); err != nil { return err } @@ -280,16 +280,16 @@ func (c GorillaNetConn) SetDeadline(t time.Time) error { return c.SetWriteDeadline(t) } -func (c GorillaNetConn) SetReadDeadline(t time.Time) error { +func (c *GorillaNetConn) SetReadDeadline(t time.Time) error { return c.Inner.SetReadDeadline(t) } -func (c GorillaNetConn) SetWriteDeadline(t time.Time) error { +func (c *GorillaNetConn) SetWriteDeadline(t time.Time) error { return c.Inner.SetWriteDeadline(t) } -func NewGorillaNetConn(raw *wsGorilla.Conn) GorillaNetConn { - return GorillaNetConn{ +func NewGorillaNetConn(raw *wsGorilla.Conn) *GorillaNetConn { + return &GorillaNetConn{ Inner: raw, DefaultMessageType: wsGorilla.BinaryMessage, } From be25e2eccbd361946097f8a3c41f5a98282538f8 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 5 Jul 2017 18:10:17 +0200 Subject: [PATCH 16/93] another import fix --- p2p/transport/websocket/websocket.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 4caffb5936..0a94b30cd6 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -13,7 +13,7 @@ import ( manet "github.com/multiformats/go-multiaddr-net" mafmt "github.com/whyrusleeping/mafmt" ws "golang.org/x/net/websocket" - wsGorilla "gx/ipfs/QmdKzkTPWmQ4nc8McYVb9TJYzRGmux9sqySQBD4nhbjQpf" + wsGorilla "gx/ipfs/QmZH5VXfAJouGMyCCHTRPGCT3e5MG9Lu78Ln3YAYW1XTts/websocket" ) var WsProtocol = ma.Protocol{ From fd70584011fa525e15db520dcd1385bc4a162c2e Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 5 Jul 2017 19:30:09 +0200 Subject: [PATCH 17/93] gx unwrite --- p2p/transport/websocket/websocket.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 0a94b30cd6..83a4e90aab 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -8,12 +8,12 @@ import ( "net/url" "time" + wsGorilla "github.com/gorilla/websocket" tpt "github.com/libp2p/go-libp2p-transport" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" mafmt "github.com/whyrusleeping/mafmt" ws "golang.org/x/net/websocket" - wsGorilla "gx/ipfs/QmZH5VXfAJouGMyCCHTRPGCT3e5MG9Lu78Ln3YAYW1XTts/websocket" ) var WsProtocol = ma.Protocol{ From 84a5d64f5a133eb4bcbc17d67b21361cf2a18463 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 9 Jul 2017 21:04:38 +0200 Subject: [PATCH 18/93] refactor: simplify and split into multiple files --- p2p/transport/websocket/addrs.go | 58 +++++ p2p/transport/websocket/addrs_test.go | 23 ++ p2p/transport/websocket/conn.go | 75 ++++++ p2p/transport/websocket/dialer.go | 41 +++ p2p/transport/websocket/listener.go | 73 ++++++ p2p/transport/websocket/websocket.go | 233 +----------------- .../{ws_test.go => websocket_test.go} | 12 - 7 files changed, 281 insertions(+), 234 deletions(-) create mode 100644 p2p/transport/websocket/addrs.go create mode 100644 p2p/transport/websocket/addrs_test.go create mode 100644 p2p/transport/websocket/conn.go create mode 100644 p2p/transport/websocket/dialer.go create mode 100644 p2p/transport/websocket/listener.go rename p2p/transport/websocket/{ws_test.go => websocket_test.go} (78%) diff --git a/p2p/transport/websocket/addrs.go b/p2p/transport/websocket/addrs.go new file mode 100644 index 0000000000..5e7cb396dd --- /dev/null +++ b/p2p/transport/websocket/addrs.go @@ -0,0 +1,58 @@ +package websocket + +import ( + "fmt" + "net" + "net/url" + + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" + ws "golang.org/x/net/websocket" +) + +func ConvertWebsocketMultiaddrToNetAddr(maddr ma.Multiaddr) (net.Addr, error) { + _, host, err := manet.DialArgs(maddr) + if err != nil { + return nil, err + } + + a := &ws.Addr{ + URL: &url.URL{ + Host: host, + }, + } + return a, nil +} + +func ParseWebsocketNetAddr(a net.Addr) (ma.Multiaddr, error) { + wsa, ok := a.(*ws.Addr) + if !ok { + return nil, fmt.Errorf("not a websocket address") + } + + tcpaddr, err := net.ResolveTCPAddr("tcp", wsa.Host) + if err != nil { + return nil, err + } + + tcpma, err := manet.FromNetAddr(tcpaddr) + if err != nil { + return nil, err + } + + wsma, err := ma.NewMultiaddr("/ws") + if err != nil { + return nil, err + } + + return tcpma.Encapsulate(wsma), nil +} + +func parseMultiaddr(a ma.Multiaddr) (string, error) { + _, host, err := manet.DialArgs(a) + if err != nil { + return "", err + } + + return "ws://" + host, nil +} diff --git a/p2p/transport/websocket/addrs_test.go b/p2p/transport/websocket/addrs_test.go new file mode 100644 index 0000000000..b85bdfda89 --- /dev/null +++ b/p2p/transport/websocket/addrs_test.go @@ -0,0 +1,23 @@ +package websocket + +import ( + "fmt" + "testing" + + ma "github.com/multiformats/go-multiaddr" +) + +func TestMultiaddrParsing(t *testing.T) { + addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") + if err != nil { + t.Fatal(err) + } + + res, err := parseMultiaddr(addr) + if err != nil { + t.Fatal(err) + } + if res != "ws://127.0.0.1:5555" { + t.Fatal(fmt.Errorf("%s != ws://127.0.0.1:5555", res)) + } +} diff --git a/p2p/transport/websocket/conn.go b/p2p/transport/websocket/conn.go new file mode 100644 index 0000000000..9a48839109 --- /dev/null +++ b/p2p/transport/websocket/conn.go @@ -0,0 +1,75 @@ +package websocket + +import ( + "net" + "time" + + ws "github.com/gorilla/websocket" +) + +var _ net.Conn = (*Conn)(nil) + +// Conn implements net.Conn interface for gorilla/websocket. +type Conn struct { + *ws.Conn + DefaultMessageType int + done func() +} + +func (c *Conn) Read(b []byte) (n int, err error) { + _, r, err := c.Conn.NextReader() + if err != nil { + return 0, err + } + + return r.Read(b) +} + +func (c *Conn) Write(b []byte) (n int, err error) { + if err := c.Conn.WriteMessage(c.DefaultMessageType, b); err != nil { + return 0, err + } + + return len(b), nil +} + +func (c *Conn) Close() error { + if c.done != nil { + c.done() + } + + return c.Conn.Close() +} + +func (c *Conn) LocalAddr() net.Addr { + return c.Conn.LocalAddr() +} + +func (c *Conn) RemoteAddr() net.Addr { + return c.Conn.RemoteAddr() +} + +func (c *Conn) SetDeadline(t time.Time) error { + if err := c.SetReadDeadline(t); err != nil { + return err + } + + return c.SetWriteDeadline(t) +} + +func (c *Conn) SetReadDeadline(t time.Time) error { + return c.Conn.SetReadDeadline(t) +} + +func (c *Conn) SetWriteDeadline(t time.Time) error { + return c.Conn.SetWriteDeadline(t) +} + +// NewConn creates a Conn given a regular gorilla/websocket Conn. +func NewConn(raw *ws.Conn, done func()) *Conn { + return &Conn{ + Conn: raw, + DefaultMessageType: ws.BinaryMessage, + done: done, + } +} diff --git a/p2p/transport/websocket/dialer.go b/p2p/transport/websocket/dialer.go new file mode 100644 index 0000000000..4c74df39d7 --- /dev/null +++ b/p2p/transport/websocket/dialer.go @@ -0,0 +1,41 @@ +package websocket + +import ( + "context" + + ws "github.com/gorilla/websocket" + tpt "github.com/libp2p/go-libp2p-transport" + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" +) + +type dialer struct{} + +func (d *dialer) Dial(raddr ma.Multiaddr) (tpt.Conn, error) { + return d.DialContext(context.Background(), raddr) +} + +func (d *dialer) DialContext(ctx context.Context, raddr ma.Multiaddr) (tpt.Conn, error) { + wsurl, err := parseMultiaddr(raddr) + if err != nil { + return nil, err + } + + wscon, _, err := ws.DefaultDialer.Dial(wsurl, nil) + if err != nil { + return nil, err + } + + mnc, err := manet.WrapNetConn(NewConn(wscon, nil)) + if err != nil { + return nil, err + } + + return &wsConn{ + Conn: mnc, + }, nil +} + +func (d *dialer) Matches(a ma.Multiaddr) bool { + return WsFmt.Matches(a) +} diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go new file mode 100644 index 0000000000..785173a0c7 --- /dev/null +++ b/p2p/transport/websocket/listener.go @@ -0,0 +1,73 @@ +package websocket + +import ( + "context" + "fmt" + "net/http" + "net/url" + + tpt "github.com/libp2p/go-libp2p-transport" + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" +) + +type wsConn struct { + manet.Conn + t tpt.Transport +} + +var _ tpt.Conn = (*wsConn)(nil) + +func (c *wsConn) Transport() tpt.Transport { + return c.t +} + +type listener struct { + manet.Listener + + incoming chan *Conn + + tpt tpt.Transport + + origin *url.URL +} + +func (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + http.Error(w, "Failed to upgrade websocket", 400) + return + } + + ctx, cancel := context.WithCancel(context.Background()) + l.incoming <- NewConn(c, cancel) + + // wait until conn gets closed, otherwise the handler closes it early + <-ctx.Done() +} + +func (l *listener) Accept() (tpt.Conn, error) { + c, ok := <-l.incoming + if !ok { + return nil, fmt.Errorf("listener is closed") + } + + mnc, err := manet.WrapNetConn(c) + if err != nil { + return nil, err + } + + return &wsConn{ + Conn: mnc, + t: l.tpt, + }, nil +} + +func (l *listener) Multiaddr() ma.Multiaddr { + wsma, err := ma.NewMultiaddr("/ws") + if err != nil { + panic(err) + } + + return l.Listener.Multiaddr().Encapsulate(wsma) +} diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 83a4e90aab..55ea54d994 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -1,29 +1,30 @@ +// Package websocket implements a websocket based transport for go-libp2p. package websocket import ( - "context" "fmt" - "net" "net/http" "net/url" - "time" - wsGorilla "github.com/gorilla/websocket" + ws "github.com/gorilla/websocket" tpt "github.com/libp2p/go-libp2p-transport" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" + mafmt "github.com/whyrusleeping/mafmt" - ws "golang.org/x/net/websocket" ) +// WsProtocol is the multiaddr protocol definition for this transport. var WsProtocol = ma.Protocol{ Code: 477, Name: "ws", VCode: ma.CodeToVarint(477), } +// WsFmt is multiaddr formatter for WsProtocol var WsFmt = mafmt.And(mafmt.TCP, mafmt.Base(WsProtocol.Code)) +// WsCodec is the multiaddr-net codec definition for the websocket transport var WsCodec = &manet.NetCodec{ NetAddrNetworks: []string{"websocket"}, ProtocolName: "ws", @@ -32,7 +33,7 @@ var WsCodec = &manet.NetCodec{ } // Default gorilla upgrader -var upgrader = wsGorilla.Upgrader{} +var upgrader = ws.Upgrader{} func init() { err := ma.AddProtocol(WsProtocol) @@ -43,46 +44,11 @@ func init() { manet.RegisterNetCodec(WsCodec) } -func ConvertWebsocketMultiaddrToNetAddr(maddr ma.Multiaddr) (net.Addr, error) { - _, host, err := manet.DialArgs(maddr) - if err != nil { - return nil, err - } - - a := &ws.Addr{ - URL: &url.URL{ - Host: host, - }, - } - return a, nil -} - -func ParseWebsocketNetAddr(a net.Addr) (ma.Multiaddr, error) { - wsa, ok := a.(*ws.Addr) - if !ok { - return nil, fmt.Errorf("not a websocket address") - } - - tcpaddr, err := net.ResolveTCPAddr("tcp", wsa.Host) - if err != nil { - return nil, err - } - - tcpma, err := manet.FromNetAddr(tcpaddr) - if err != nil { - return nil, err - } - - wsma, err := ma.NewMultiaddr("/ws") - if err != nil { - return nil, err - } - - return tcpma.Encapsulate(wsma), nil -} - +// WebsocketTransport is the actual go-libp2p transport type WebsocketTransport struct{} +var _ tpt.Transport = (*WebsocketTransport)(nil) + func (t *WebsocketTransport) Matches(a ma.Multiaddr) bool { return WsFmt.Matches(a) } @@ -91,79 +57,6 @@ func (t *WebsocketTransport) Dialer(_ ma.Multiaddr, opts ...tpt.DialOpt) (tpt.Di return &dialer{}, nil } -type dialer struct{} - -func parseMultiaddr(a ma.Multiaddr) (string, error) { - _, host, err := manet.DialArgs(a) - if err != nil { - return "", err - } - - return "ws://" + host, nil -} - -func (d *dialer) Dial(raddr ma.Multiaddr) (tpt.Conn, error) { - return d.DialContext(context.Background(), raddr) -} - -func (d *dialer) DialContext(ctx context.Context, raddr ma.Multiaddr) (tpt.Conn, error) { - wsurl, err := parseMultiaddr(raddr) - if err != nil { - return nil, err - } - - // TODO: figure out origins, probably don't work for us - // header := http.Header{} - // header.Set("Origin", "http://127.0.0.1:0/") - wscon, _, err := wsGorilla.DefaultDialer.Dial(wsurl, nil) - if err != nil { - return nil, err - } - - mnc, err := manet.WrapNetConn(NewGorillaNetConn(wscon)) - if err != nil { - return nil, err - } - - return &wsConn{ - Conn: mnc, - }, nil -} - -func (d *dialer) Matches(a ma.Multiaddr) bool { - return WsFmt.Matches(a) -} - -type wsConn struct { - manet.Conn - t tpt.Transport -} - -func (c *wsConn) Transport() tpt.Transport { - return c.t -} - -type listener struct { - manet.Listener - - incoming chan *conn - - tpt tpt.Transport - - origin *url.URL -} - -type conn struct { - *GorillaNetConn - - done func() -} - -func (c *conn) Close() error { - c.done() - return c.GorillaNetConn.Close() -} - func (t *WebsocketTransport) Listen(a ma.Multiaddr) (tpt.Listener, error) { list, err := manet.Listen(a) if err != nil { @@ -185,112 +78,8 @@ func (t *WebsocketTransport) Listen(a ma.Multiaddr) (tpt.Listener, error) { func (t *WebsocketTransport) wrapListener(l manet.Listener, origin *url.URL) *listener { return &listener{ Listener: l, - incoming: make(chan *conn), + incoming: make(chan *Conn), tpt: t, origin: origin, } } - -func (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { - c, err := upgrader.Upgrade(w, r, nil) - if err != nil { - http.Error(w, "Failed to upgrade websocket", 400) - return - } - - ctx, cancel := context.WithCancel(context.Background()) - - wrapped := NewGorillaNetConn(c) - l.incoming <- &conn{ - GorillaNetConn: wrapped, - done: cancel, - } - - // wait until conn gets closed, otherwise the handler closes it early - <-ctx.Done() -} - -func (l *listener) Accept() (tpt.Conn, error) { - c, ok := <-l.incoming - if !ok { - return nil, fmt.Errorf("listener is closed") - } - - mnc, err := manet.WrapNetConn(c) - if err != nil { - return nil, err - } - - return &wsConn{ - Conn: mnc, - t: l.tpt, - }, nil -} - -func (l *listener) Multiaddr() ma.Multiaddr { - wsma, err := ma.NewMultiaddr("/ws") - if err != nil { - panic(err) - } - - return l.Listener.Multiaddr().Encapsulate(wsma) -} - -var _ tpt.Transport = (*WebsocketTransport)(nil) - -type GorillaNetConn struct { - Inner *wsGorilla.Conn - DefaultMessageType int -} - -func (c *GorillaNetConn) Read(b []byte) (n int, err error) { - _, r, err := c.Inner.NextReader() - if err != nil { - return 0, err - } - - return r.Read(b) -} - -func (c *GorillaNetConn) Write(b []byte) (n int, err error) { - if err := c.Inner.WriteMessage(c.DefaultMessageType, b); err != nil { - return 0, err - } - - return len(b), nil -} - -func (c *GorillaNetConn) Close() error { - return c.Inner.Close() -} - -func (c *GorillaNetConn) LocalAddr() net.Addr { - return c.Inner.LocalAddr() -} - -func (c *GorillaNetConn) RemoteAddr() net.Addr { - return c.Inner.RemoteAddr() -} - -func (c *GorillaNetConn) SetDeadline(t time.Time) error { - if err := c.SetReadDeadline(t); err != nil { - return err - } - - return c.SetWriteDeadline(t) -} - -func (c *GorillaNetConn) SetReadDeadline(t time.Time) error { - return c.Inner.SetReadDeadline(t) -} - -func (c *GorillaNetConn) SetWriteDeadline(t time.Time) error { - return c.Inner.SetWriteDeadline(t) -} - -func NewGorillaNetConn(raw *wsGorilla.Conn) *GorillaNetConn { - return &GorillaNetConn{ - Inner: raw, - DefaultMessageType: wsGorilla.BinaryMessage, - } -} diff --git a/p2p/transport/websocket/ws_test.go b/p2p/transport/websocket/websocket_test.go similarity index 78% rename from p2p/transport/websocket/ws_test.go rename to p2p/transport/websocket/websocket_test.go index 1667ac42b8..f56159f200 100644 --- a/p2p/transport/websocket/ws_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -7,18 +7,6 @@ import ( ma "github.com/multiformats/go-multiaddr" ) -func TestMultiaddrParsing(t *testing.T) { - addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") - if err != nil { - t.Fatal(err) - } - - _, err = parseMultiaddr(addr) - if err != nil { - t.Fatal(err) - } -} - func TestWebsocketListen(t *testing.T) { zero, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0/ws") if err != nil { From 328039237d7868ee510c67b9cd681a41e501cfb0 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 9 Jul 2017 21:47:26 +0200 Subject: [PATCH 19/93] more tests --- p2p/transport/websocket/addrs.go | 31 +++++++++++----- p2p/transport/websocket/addrs_test.go | 52 ++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/p2p/transport/websocket/addrs.go b/p2p/transport/websocket/addrs.go index 5e7cb396dd..e5dbc46e43 100644 --- a/p2p/transport/websocket/addrs.go +++ b/p2p/transport/websocket/addrs.go @@ -7,25 +7,40 @@ import ( ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" - ws "golang.org/x/net/websocket" ) +// Addr is an implementation of net.Addr for WebSocket. +type Addr struct { + *url.URL +} + +var _ net.Addr = (*Addr)(nil) + +// Network returns the network type for a WebSocket, "websocket". +func (addr *Addr) Network() string { + return "websocket" +} + +// NewAddr creates a new Addr using the given host string +func NewAddr(host string) *Addr { + return &Addr{ + URL: &url.URL{ + Host: host, + }, + } +} + func ConvertWebsocketMultiaddrToNetAddr(maddr ma.Multiaddr) (net.Addr, error) { _, host, err := manet.DialArgs(maddr) if err != nil { return nil, err } - a := &ws.Addr{ - URL: &url.URL{ - Host: host, - }, - } - return a, nil + return NewAddr(host), nil } func ParseWebsocketNetAddr(a net.Addr) (ma.Multiaddr, error) { - wsa, ok := a.(*ws.Addr) + wsa, ok := a.(*Addr) if !ok { return nil, fmt.Errorf("not a websocket address") } diff --git a/p2p/transport/websocket/addrs_test.go b/p2p/transport/websocket/addrs_test.go index b85bdfda89..d962760088 100644 --- a/p2p/transport/websocket/addrs_test.go +++ b/p2p/transport/websocket/addrs_test.go @@ -1,7 +1,7 @@ package websocket import ( - "fmt" + "net/url" "testing" ma "github.com/multiformats/go-multiaddr" @@ -13,11 +13,55 @@ func TestMultiaddrParsing(t *testing.T) { t.Fatal(err) } - res, err := parseMultiaddr(addr) + wsaddr, err := parseMultiaddr(addr) if err != nil { t.Fatal(err) } - if res != "ws://127.0.0.1:5555" { - t.Fatal(fmt.Errorf("%s != ws://127.0.0.1:5555", res)) + if wsaddr != "ws://127.0.0.1:5555" { + t.Fatalf("expected ws://127.0.0.1:5555, got %s", wsaddr) + } +} + +type httpAddr struct { + *url.URL +} + +func (addr *httpAddr) Network() string { + return "http" +} + +func TestParseWebsocketNetAddr(t *testing.T) { + notWs := &httpAddr{&url.URL{Host: "http://127.0.0.1:1234"}} + _, err := ParseWebsocketNetAddr(notWs) + if err.Error() != "not a websocket address" { + t.Fatalf("expect \"not a websocket address\", got \"%s\"", err) + } + + wsAddr := NewAddr("127.0.0.1:5555") + parsed, err := ParseWebsocketNetAddr(wsAddr) + if err != nil { + t.Fatal(err) + } + + if parsed.String() != "/ip4/127.0.0.1/tcp/5555/ws" { + t.Fatalf("expected \"/ip4/127.0.0.1/tcp/5555/ws\", got \"%s\"", parsed.String()) + } +} + +func TestConvertWebsocketMultiaddrToNetAddr(t *testing.T) { + addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") + if err != nil { + t.Fatal(err) + } + + wsaddr, err := ConvertWebsocketMultiaddrToNetAddr(addr) + if err != nil { + t.Fatal(err) + } + if wsaddr.String() != "//127.0.0.1:5555" { + t.Fatalf("expected //127.0.0.1:5555, got %s", wsaddr) + } + if wsaddr.Network() != "websocket" { + t.Fatalf("expected network: \"websocket\", got \"%s\"", wsaddr.Network()) } } From 9b327a62a9cc9e479cfff110f5a30ca016c6f3c0 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sun, 9 Jul 2017 21:56:19 +0200 Subject: [PATCH 20/93] add dialer.Matches test --- p2p/transport/websocket/dialer_test.go | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 p2p/transport/websocket/dialer_test.go diff --git a/p2p/transport/websocket/dialer_test.go b/p2p/transport/websocket/dialer_test.go new file mode 100644 index 0000000000..2e8b5caf59 --- /dev/null +++ b/p2p/transport/websocket/dialer_test.go @@ -0,0 +1,31 @@ +package websocket + +import ( + "testing" + + ma "github.com/multiformats/go-multiaddr" +) + +func TestDialerMatches(t *testing.T) { + addrWs, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") + if err != nil { + t.Fatal(err) + } + + addrTcp, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555") + if err != nil { + t.Fatal(err) + } + + d := &dialer{} + matchTrue := d.Matches(addrWs) + matchFalse := d.Matches(addrTcp) + + if !matchTrue { + t.Fatal("expected to match websocket maddr, but did not") + } + + if matchFalse { + t.Fatal("expected to not match tcp maddr, but did") + } +} From efba55492e4ca25ce3ff1597909dde851ae5749d Mon Sep 17 00:00:00 2001 From: Jeromy Date: Mon, 4 Sep 2017 21:54:39 -0700 Subject: [PATCH 21/93] fix reading from conn and addresses --- p2p/transport/websocket/conn.go | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/p2p/transport/websocket/conn.go b/p2p/transport/websocket/conn.go index 9a48839109..9f1973474c 100644 --- a/p2p/transport/websocket/conn.go +++ b/p2p/transport/websocket/conn.go @@ -1,6 +1,7 @@ package websocket import ( + "io" "net" "time" @@ -14,15 +15,37 @@ type Conn struct { *ws.Conn DefaultMessageType int done func() + reader io.Reader } -func (c *Conn) Read(b []byte) (n int, err error) { +func (c *Conn) Read(b []byte) (int, error) { + if c.reader == nil { + if err := c.prepNextReader(); err != nil { + return 0, err + } + } + + n, err := c.reader.Read(b) + if err == io.EOF { + if err := c.prepNextReader(); err != nil { + return 0, err + } + if n == 0 { + n, err = c.reader.Read(b) + } + } + + return n, err +} + +func (c *Conn) prepNextReader() error { _, r, err := c.Conn.NextReader() if err != nil { - return 0, err + return err } - return r.Read(b) + c.reader = r + return nil } func (c *Conn) Write(b []byte) (n int, err error) { @@ -42,11 +65,11 @@ func (c *Conn) Close() error { } func (c *Conn) LocalAddr() net.Addr { - return c.Conn.LocalAddr() + return NewAddr(c.Conn.LocalAddr().String()) } func (c *Conn) RemoteAddr() net.Addr { - return c.Conn.RemoteAddr() + return NewAddr(c.Conn.RemoteAddr().String()) } func (c *Conn) SetDeadline(t time.Time) error { From cb1cddaeb6421ded8886e1278d055c21a9ff4e3e Mon Sep 17 00:00:00 2001 From: Jeromy Date: Mon, 4 Sep 2017 22:27:51 -0700 Subject: [PATCH 22/93] fix review and tests --- p2p/transport/websocket/conn.go | 38 +++++++++++++++++------ p2p/transport/websocket/websocket_test.go | 11 ++++--- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/p2p/transport/websocket/conn.go b/p2p/transport/websocket/conn.go index 9f1973474c..1d8de2a01f 100644 --- a/p2p/transport/websocket/conn.go +++ b/p2p/transport/websocket/conn.go @@ -25,25 +25,42 @@ func (c *Conn) Read(b []byte) (int, error) { } } - n, err := c.reader.Read(b) - if err == io.EOF { - if err := c.prepNextReader(); err != nil { - return 0, err - } - if n == 0 { - n, err = c.reader.Read(b) + for { + n, err := c.reader.Read(b) + switch err { + case io.EOF: + c.reader = nil + + if n > 0 { + return n, nil + } + + if err := c.prepNextReader(); err != nil { + return 0, err + } + + // explicitly looping + default: + return n, err } } - - return n, err } func (c *Conn) prepNextReader() error { - _, r, err := c.Conn.NextReader() + t, r, err := c.Conn.NextReader() if err != nil { + if wserr, ok := err.(*ws.CloseError); ok { + if wserr.Code == 1000 || wserr.Code == 1005 { + return io.EOF + } + } return err } + if t == ws.CloseMessage { + return io.EOF + } + c.reader = r return nil } @@ -61,6 +78,7 @@ func (c *Conn) Close() error { c.done() } + c.Conn.WriteMessage(ws.CloseMessage, nil) return c.Conn.Close() } diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index f56159f200..7aab918576 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -2,7 +2,9 @@ package websocket import ( "bytes" + "io/ioutil" "testing" + "testing/iotest" ma "github.com/multiformats/go-multiaddr" ) @@ -40,13 +42,14 @@ func TestWebsocketListen(t *testing.T) { } defer c.Close() - buf := make([]byte, 32) - n, err := c.Read(buf) + obr := iotest.OneByteReader(c) + + out, err := ioutil.ReadAll(obr) if err != nil { t.Fatal(err) } - if !bytes.Equal(buf[:n], msg) { - t.Fatal("got wrong message", buf[:n], msg) + if !bytes.Equal(out, msg) { + t.Fatal("got wrong message", out, msg) } } From a293989e7c4c82d2228bff8c8470a71f6f8d8df8 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 5 Sep 2017 11:48:46 -0700 Subject: [PATCH 23/93] Make close thread safe 1. Ensure we only close the connection once. Especially, don't call the done function multiple times and/or concurrently. 2. Call WriteControl instead of WriteMessage in Close. WriteControl is thread-safe, WriteMessage isn't. Also, this sets a 100ms deadline on gracefully closing connections. --- p2p/transport/websocket/conn.go | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/p2p/transport/websocket/conn.go b/p2p/transport/websocket/conn.go index 1d8de2a01f..0ab71ce220 100644 --- a/p2p/transport/websocket/conn.go +++ b/p2p/transport/websocket/conn.go @@ -3,11 +3,16 @@ package websocket import ( "io" "net" + "sync" "time" ws "github.com/gorilla/websocket" ) +// GracefulCloseTimeout is the time to wait trying to gracefully close a +// connection before simply cutting it. +var GracefulCloseTimeout = 100 * time.Millisecond + var _ net.Conn = (*Conn)(nil) // Conn implements net.Conn interface for gorilla/websocket. @@ -16,6 +21,7 @@ type Conn struct { DefaultMessageType int done func() reader io.Reader + closeOnce sync.Once } func (c *Conn) Read(b []byte) (int, error) { @@ -73,13 +79,22 @@ func (c *Conn) Write(b []byte) (n int, err error) { return len(b), nil } +// Close closes the connection. Only the first call to Close will receive the +// close error, subsequent and concurrent calls will return nil. +// This method is thread-safe. func (c *Conn) Close() error { - if c.done != nil { - c.done() - } + var err error = nil + c.closeOnce.Do(func() { + if c.done != nil { + c.done() + // Be nice to GC + c.done = nil + } - c.Conn.WriteMessage(ws.CloseMessage, nil) - return c.Conn.Close() + c.Conn.WriteControl(ws.CloseMessage, nil, time.Now().Add(GracefulCloseTimeout)) + err = c.Conn.Close() + }) + return err } func (c *Conn) LocalAddr() net.Addr { From b863f44dabd3c8a230a6c4f0b57cfeee4576abc8 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 5 Sep 2017 12:12:21 -0700 Subject: [PATCH 24/93] test concurrent connection closing --- p2p/transport/websocket/websocket_test.go | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index 7aab918576..d2e166a685 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -53,3 +53,41 @@ func TestWebsocketListen(t *testing.T) { t.Fatal("got wrong message", out, msg) } } + +func TestConcurrentClose(t *testing.T) { + zero, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0/ws") + if err != nil { + t.Fatal(err) + } + + tpt := &WebsocketTransport{} + l, err := tpt.Listen(zero) + if err != nil { + t.Fatal(err) + } + defer l.Close() + + msg := []byte("HELLO WORLD") + + go func() { + d, _ := tpt.Dialer(nil) + for i := 0; i < 100; i++ { + c, err := d.Dial(l.Multiaddr()) + if err != nil { + t.Error(err) + return + } + + go c.Write(msg) + go c.Close() + } + }() + + for i := 0; i < 100; i++ { + c, err := l.Accept() + if err != nil { + t.Fatal(err) + } + c.Close() + } +} From a88b185db2f234ec5743b9ec9810437fa856e669 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 5 Sep 2017 12:25:29 -0700 Subject: [PATCH 25/93] Add write-zero test. Just to be thorough. --- p2p/transport/websocket/websocket_test.go | 52 +++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index d2e166a685..61bfa14230 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -2,6 +2,7 @@ package websocket import ( "bytes" + "io" "io/ioutil" "testing" "testing/iotest" @@ -91,3 +92,54 @@ func TestConcurrentClose(t *testing.T) { c.Close() } } + +func TestWriteZero(t *testing.T) { + zero, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0/ws") + if err != nil { + t.Fatal(err) + } + + tpt := &WebsocketTransport{} + l, err := tpt.Listen(zero) + if err != nil { + t.Fatal(err) + } + defer l.Close() + + msg := []byte(nil) + + go func() { + d, _ := tpt.Dialer(nil) + c, err := d.Dial(l.Multiaddr()) + defer c.Close() + if err != nil { + t.Error(err) + return + } + + for i := 0; i < 100; i++ { + n, err := c.Write(msg) + if n != 0 { + t.Errorf("expected to write 0 bytes, wrote %d", n) + } + if err != nil { + t.Error(err) + return + } + } + }() + + c, err := l.Accept() + defer c.Close() + if err != nil { + t.Fatal(err) + } + buf := make([]byte, 100) + n, err := c.Read(buf) + if n != 0 { + t.Errorf("read %d bytes, expected 0", n) + } + if err != io.EOF { + t.Errorf("expected EOF, got err: %s", err) + } +} From da59505d57802250e79733282e7a296c8f81db13 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 5 Sep 2017 12:27:45 -0700 Subject: [PATCH 26/93] don't explicitly nil err (make @whyrusleeping happy) --- p2p/transport/websocket/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/websocket/conn.go b/p2p/transport/websocket/conn.go index 0ab71ce220..168497bf39 100644 --- a/p2p/transport/websocket/conn.go +++ b/p2p/transport/websocket/conn.go @@ -83,7 +83,7 @@ func (c *Conn) Write(b []byte) (n int, err error) { // close error, subsequent and concurrent calls will return nil. // This method is thread-safe. func (c *Conn) Close() error { - var err error = nil + var err error c.closeOnce.Do(func() { if c.done != nil { c.done() From f4559306534f037b0fa71200b678e13b7cebdf37 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 5 Sep 2017 14:56:26 -0700 Subject: [PATCH 27/93] fix requests from other origins We don't care about origins and *want* any code running on any origin to be able to connect to any peer. fixes ipfs/go-ipfs#4202 --- p2p/transport/websocket/websocket.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 55ea54d994..6a2c6722e5 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -33,7 +33,12 @@ var WsCodec = &manet.NetCodec{ } // Default gorilla upgrader -var upgrader = ws.Upgrader{} +var upgrader = ws.Upgrader{ + // Allow requests from *all* origins. + CheckOrigin: func(r *http.Request) bool { + return true + }, +} func init() { err := ma.AddProtocol(WsProtocol) From 2d2035f7575dc3b339d5f81529741eb71bee7045 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 29 Nov 2017 10:31:36 -0800 Subject: [PATCH 28/93] close connections we fail to wrap Maybe related to #21. Probably not. --- p2p/transport/websocket/listener.go | 1 + 1 file changed, 1 insertion(+) diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go index 785173a0c7..146b371a9f 100644 --- a/p2p/transport/websocket/listener.go +++ b/p2p/transport/websocket/listener.go @@ -54,6 +54,7 @@ func (l *listener) Accept() (tpt.Conn, error) { mnc, err := manet.WrapNetConn(c) if err != nil { + c.Close() return nil, err } From 250a23c6dbf6a658d094e297ce4ac3fa254879f2 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sun, 3 Dec 2017 14:24:53 -0800 Subject: [PATCH 29/93] close connections we fail to dial The previous commit did this for connections we accept but I forgot to do this for dialed connections. --- p2p/transport/websocket/dialer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/p2p/transport/websocket/dialer.go b/p2p/transport/websocket/dialer.go index 4c74df39d7..7a5a91579a 100644 --- a/p2p/transport/websocket/dialer.go +++ b/p2p/transport/websocket/dialer.go @@ -28,6 +28,7 @@ func (d *dialer) DialContext(ctx context.Context, raddr ma.Multiaddr) (tpt.Conn, mnc, err := manet.WrapNetConn(NewConn(wscon, nil)) if err != nil { + wscon.Close() return nil, err } From 42780280b424e6b3aca737481fdc6be983049340 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 15 Feb 2018 20:01:38 -0800 Subject: [PATCH 30/93] fixup for transport refactor Also, use the now standardized transport tests (and fix bugs around closing the listener). --- p2p/transport/websocket/dialer.go | 42 ---------- p2p/transport/websocket/dialer_test.go | 31 ------- p2p/transport/websocket/listener.go | 88 +++++++++++--------- p2p/transport/websocket/websocket.go | 98 +++++++++++++++++++---- p2p/transport/websocket/websocket_test.go | 58 +++++++++++--- 5 files changed, 182 insertions(+), 135 deletions(-) delete mode 100644 p2p/transport/websocket/dialer.go delete mode 100644 p2p/transport/websocket/dialer_test.go diff --git a/p2p/transport/websocket/dialer.go b/p2p/transport/websocket/dialer.go deleted file mode 100644 index 7a5a91579a..0000000000 --- a/p2p/transport/websocket/dialer.go +++ /dev/null @@ -1,42 +0,0 @@ -package websocket - -import ( - "context" - - ws "github.com/gorilla/websocket" - tpt "github.com/libp2p/go-libp2p-transport" - ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr-net" -) - -type dialer struct{} - -func (d *dialer) Dial(raddr ma.Multiaddr) (tpt.Conn, error) { - return d.DialContext(context.Background(), raddr) -} - -func (d *dialer) DialContext(ctx context.Context, raddr ma.Multiaddr) (tpt.Conn, error) { - wsurl, err := parseMultiaddr(raddr) - if err != nil { - return nil, err - } - - wscon, _, err := ws.DefaultDialer.Dial(wsurl, nil) - if err != nil { - return nil, err - } - - mnc, err := manet.WrapNetConn(NewConn(wscon, nil)) - if err != nil { - wscon.Close() - return nil, err - } - - return &wsConn{ - Conn: mnc, - }, nil -} - -func (d *dialer) Matches(a ma.Multiaddr) bool { - return WsFmt.Matches(a) -} diff --git a/p2p/transport/websocket/dialer_test.go b/p2p/transport/websocket/dialer_test.go deleted file mode 100644 index 2e8b5caf59..0000000000 --- a/p2p/transport/websocket/dialer_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package websocket - -import ( - "testing" - - ma "github.com/multiformats/go-multiaddr" -) - -func TestDialerMatches(t *testing.T) { - addrWs, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") - if err != nil { - t.Fatal(err) - } - - addrTcp, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555") - if err != nil { - t.Fatal(err) - } - - d := &dialer{} - matchTrue := d.Matches(addrWs) - matchFalse := d.Matches(addrTcp) - - if !matchTrue { - t.Fatal("expected to match websocket maddr, but did not") - } - - if matchFalse { - t.Fatal("expected to not match tcp maddr, but did") - } -} diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go index 146b371a9f..3d774f09dc 100644 --- a/p2p/transport/websocket/listener.go +++ b/p2p/transport/websocket/listener.go @@ -3,33 +3,25 @@ package websocket import ( "context" "fmt" + "net" "net/http" - "net/url" - tpt "github.com/libp2p/go-libp2p-transport" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" ) -type wsConn struct { - manet.Conn - t tpt.Transport -} - -var _ tpt.Conn = (*wsConn)(nil) - -func (c *wsConn) Transport() tpt.Transport { - return c.t -} - type listener struct { - manet.Listener + net.Listener - incoming chan *Conn + laddr ma.Multiaddr - tpt tpt.Transport + closed chan struct{} + incoming chan *Conn +} - origin *url.URL +func (l *listener) serve() { + defer close(l.closed) + http.Serve(l.Listener, l) } func (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -40,35 +32,55 @@ func (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { } ctx, cancel := context.WithCancel(context.Background()) - l.incoming <- NewConn(c, cancel) - - // wait until conn gets closed, otherwise the handler closes it early - <-ctx.Done() -} -func (l *listener) Accept() (tpt.Conn, error) { - c, ok := <-l.incoming - if !ok { - return nil, fmt.Errorf("listener is closed") + var cnCh <-chan bool + if cn, ok := w.(http.CloseNotifier); ok { + cnCh = cn.CloseNotify() } - mnc, err := manet.WrapNetConn(c) - if err != nil { + wscon := NewConn(c, cancel) + // Just to make sure. + defer wscon.Close() + + select { + case l.incoming <- wscon: + case <-l.closed: c.Close() - return nil, err + return + case <-cnCh: + return } - return &wsConn{ - Conn: mnc, - t: l.tpt, - }, nil + // wait until conn gets closed, otherwise the handler closes it early + select { + case <-ctx.Done(): + case <-l.closed: + c.Close() + return + case <-cnCh: + return + } } -func (l *listener) Multiaddr() ma.Multiaddr { - wsma, err := ma.NewMultiaddr("/ws") - if err != nil { - panic(err) +func (l *listener) Accept() (manet.Conn, error) { + select { + case c, ok := <-l.incoming: + if !ok { + return nil, fmt.Errorf("listener is closed") + } + + mnc, err := manet.WrapNetConn(c) + if err != nil { + c.Close() + return nil, err + } + + return mnc, nil + case <-l.closed: + return nil, fmt.Errorf("listener is closed") } +} - return l.Listener.Multiaddr().Encapsulate(wsma) +func (l *listener) Multiaddr() ma.Multiaddr { + return l.laddr } diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 6a2c6722e5..b9ec1d6a9a 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -2,15 +2,18 @@ package websocket import ( + "context" "fmt" + "net" "net/http" "net/url" ws "github.com/gorilla/websocket" + peer "github.com/libp2p/go-libp2p-peer" tpt "github.com/libp2p/go-libp2p-transport" + tptu "github.com/libp2p/go-libp2p-transport-upgrader" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" - mafmt "github.com/whyrusleeping/mafmt" ) @@ -50,41 +53,106 @@ func init() { } // WebsocketTransport is the actual go-libp2p transport -type WebsocketTransport struct{} +type WebsocketTransport struct { + Upgrader *tptu.Upgrader +} + +func New(u *tptu.Upgrader) *WebsocketTransport { + return &WebsocketTransport{u} +} var _ tpt.Transport = (*WebsocketTransport)(nil) -func (t *WebsocketTransport) Matches(a ma.Multiaddr) bool { +func (t *WebsocketTransport) CanDial(a ma.Multiaddr) bool { return WsFmt.Matches(a) } -func (t *WebsocketTransport) Dialer(_ ma.Multiaddr, opts ...tpt.DialOpt) (tpt.Dialer, error) { - return &dialer{}, nil +func (t *WebsocketTransport) Protocols() []int { + return []int{WsProtocol.Code} } -func (t *WebsocketTransport) Listen(a ma.Multiaddr) (tpt.Listener, error) { - list, err := manet.Listen(a) +func (t *WebsocketTransport) Proxy() bool { + return false +} + +func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) { + wsurl, err := parseMultiaddr(raddr) + if err != nil { + return nil, err + } + + wscon, _, err := ws.DefaultDialer.Dial(wsurl, nil) + if err != nil { + return nil, err + } + + mnc, err := manet.WrapNetConn(NewConn(wscon, nil)) + if err != nil { + wscon.Close() + return nil, err + } + return mnc, nil +} + +func (t *WebsocketTransport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tpt.Conn, error) { + macon, err := t.maDial(ctx, raddr) + if err != nil { + return nil, err + } + return t.Upgrader.UpgradeOutbound(ctx, t, macon, p) +} + +func (t *WebsocketTransport) maListen(a ma.Multiaddr) (manet.Listener, error) { + lnet, lnaddr, err := manet.DialArgs(a) + if err != nil { + return nil, err + } + + nl, err := net.Listen(lnet, lnaddr) if err != nil { return nil, err } - u, err := url.Parse("http://" + list.Addr().String()) + u, err := url.Parse("http://" + nl.Addr().String()) if err != nil { + nl.Close() return nil, err } - tlist := t.wrapListener(list, u) + malist, err := t.wrapListener(nl, u) + if err != nil { + nl.Close() + return nil, err + } - go http.Serve(list.NetListener(), tlist) + go malist.serve() - return tlist, nil + return malist, nil } -func (t *WebsocketTransport) wrapListener(l manet.Listener, origin *url.URL) *listener { +func (t *WebsocketTransport) Listen(a ma.Multiaddr) (tpt.Listener, error) { + malist, err := t.maListen(a) + if err != nil { + return nil, err + } + return t.Upgrader.UpgradeListener(t, malist), nil +} + +func (t *WebsocketTransport) wrapListener(l net.Listener, origin *url.URL) (*listener, error) { + laddr, err := manet.FromNetAddr(l.Addr()) + if err != nil { + return nil, err + } + wsma, err := ma.NewMultiaddr("/ws") + if err != nil { + return nil, err + } + laddr = laddr.Encapsulate(wsma) + return &listener{ + laddr: laddr, Listener: l, incoming: make(chan *Conn), - tpt: t, - origin: origin, - } + closed: make(chan struct{}), + }, nil } diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index 61bfa14230..2c9ad32441 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -2,14 +2,57 @@ package websocket import ( "bytes" + "context" "io" "io/ioutil" "testing" "testing/iotest" + insecure "github.com/libp2p/go-conn-security/insecure" + tptu "github.com/libp2p/go-libp2p-transport-upgrader" + utils "github.com/libp2p/go-libp2p-transport/test" ma "github.com/multiformats/go-multiaddr" + mplex "github.com/whyrusleeping/go-smux-multiplex" ) +func TestCanDial(t *testing.T) { + addrWs, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") + if err != nil { + t.Fatal(err) + } + + addrTCP, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555") + if err != nil { + t.Fatal(err) + } + + d := &WebsocketTransport{} + matchTrue := d.CanDial(addrWs) + matchFalse := d.CanDial(addrTCP) + + if !matchTrue { + t.Fatal("expected to match websocket maddr, but did not") + } + + if matchFalse { + t.Fatal("expected to not match tcp maddr, but did") + } +} + +func TestWebsocketTransport(t *testing.T) { + ta := New(&tptu.Upgrader{ + Secure: insecure.New("peerA"), + Muxer: new(mplex.Transport), + }) + tb := New(&tptu.Upgrader{ + Secure: insecure.New("peerB"), + Muxer: new(mplex.Transport), + }) + + zero := "/ip4/127.0.0.1/tcp/0/ws" + utils.SubtestTransport(t, ta, tb, zero, "peerA") +} + func TestWebsocketListen(t *testing.T) { zero, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0/ws") if err != nil { @@ -17,7 +60,7 @@ func TestWebsocketListen(t *testing.T) { } tpt := &WebsocketTransport{} - l, err := tpt.Listen(zero) + l, err := tpt.maListen(zero) if err != nil { t.Fatal(err) } @@ -26,8 +69,7 @@ func TestWebsocketListen(t *testing.T) { msg := []byte("HELLO WORLD") go func() { - d, _ := tpt.Dialer(nil) - c, err := d.Dial(l.Multiaddr()) + c, err := tpt.maDial(context.Background(), l.Multiaddr()) if err != nil { t.Error(err) return @@ -62,7 +104,7 @@ func TestConcurrentClose(t *testing.T) { } tpt := &WebsocketTransport{} - l, err := tpt.Listen(zero) + l, err := tpt.maListen(zero) if err != nil { t.Fatal(err) } @@ -71,9 +113,8 @@ func TestConcurrentClose(t *testing.T) { msg := []byte("HELLO WORLD") go func() { - d, _ := tpt.Dialer(nil) for i := 0; i < 100; i++ { - c, err := d.Dial(l.Multiaddr()) + c, err := tpt.maDial(context.Background(), l.Multiaddr()) if err != nil { t.Error(err) return @@ -100,7 +141,7 @@ func TestWriteZero(t *testing.T) { } tpt := &WebsocketTransport{} - l, err := tpt.Listen(zero) + l, err := tpt.maListen(zero) if err != nil { t.Fatal(err) } @@ -109,8 +150,7 @@ func TestWriteZero(t *testing.T) { msg := []byte(nil) go func() { - d, _ := tpt.Dialer(nil) - c, err := d.Dial(l.Multiaddr()) + c, err := tpt.maDial(context.Background(), l.Multiaddr()) defer c.Close() if err != nil { t.Error(err) From 16e9eb2bb0f2916c9fa5667c663d955ef4a8886d Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 3 Apr 2019 23:17:58 -0700 Subject: [PATCH 31/93] modernize request handling I believe this was written before request hijacking was a thing. We no longer need to hold the `ServeHTTP` function open. --- p2p/transport/websocket/conn.go | 18 ++++++-------- p2p/transport/websocket/listener.go | 30 +++-------------------- p2p/transport/websocket/websocket.go | 2 +- p2p/transport/websocket/websocket_test.go | 18 +++++++++++--- 4 files changed, 26 insertions(+), 42 deletions(-) diff --git a/p2p/transport/websocket/conn.go b/p2p/transport/websocket/conn.go index 168497bf39..b63a28a9ed 100644 --- a/p2p/transport/websocket/conn.go +++ b/p2p/transport/websocket/conn.go @@ -19,7 +19,6 @@ var _ net.Conn = (*Conn)(nil) type Conn struct { *ws.Conn DefaultMessageType int - done func() reader io.Reader closeOnce sync.Once } @@ -85,14 +84,14 @@ func (c *Conn) Write(b []byte) (n int, err error) { func (c *Conn) Close() error { var err error c.closeOnce.Do(func() { - if c.done != nil { - c.done() - // Be nice to GC - c.done = nil + err1 := c.Conn.WriteControl(ws.CloseMessage, nil, time.Now().Add(GracefulCloseTimeout)) + err2 := c.Conn.Close() + switch { + case err1 != nil: + err = err1 + case err2 != nil: + err = err2 } - - c.Conn.WriteControl(ws.CloseMessage, nil, time.Now().Add(GracefulCloseTimeout)) - err = c.Conn.Close() }) return err } @@ -122,10 +121,9 @@ func (c *Conn) SetWriteDeadline(t time.Time) error { } // NewConn creates a Conn given a regular gorilla/websocket Conn. -func NewConn(raw *ws.Conn, done func()) *Conn { +func NewConn(raw *ws.Conn) *Conn { return &Conn{ Conn: raw, DefaultMessageType: ws.BinaryMessage, - done: done, } } diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go index 3d774f09dc..437be9893f 100644 --- a/p2p/transport/websocket/listener.go +++ b/p2p/transport/websocket/listener.go @@ -1,7 +1,6 @@ package websocket import ( - "context" "fmt" "net" "net/http" @@ -21,7 +20,7 @@ type listener struct { func (l *listener) serve() { defer close(l.closed) - http.Serve(l.Listener, l) + _ = http.Serve(l.Listener, l) } func (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -31,35 +30,12 @@ func (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - ctx, cancel := context.WithCancel(context.Background()) - - var cnCh <-chan bool - if cn, ok := w.(http.CloseNotifier); ok { - cnCh = cn.CloseNotify() - } - - wscon := NewConn(c, cancel) - // Just to make sure. - defer wscon.Close() - select { - case l.incoming <- wscon: + case l.incoming <- NewConn(c): case <-l.closed: c.Close() - return - case <-cnCh: - return - } - - // wait until conn gets closed, otherwise the handler closes it early - select { - case <-ctx.Done(): - case <-l.closed: - c.Close() - return - case <-cnCh: - return } + // The connection has been hijacked, it's safe to return. } func (l *listener) Accept() (manet.Conn, error) { diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index b9ec1d6a9a..681d67640c 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -86,7 +86,7 @@ func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (ma return nil, err } - mnc, err := manet.WrapNetConn(NewConn(wscon, nil)) + mnc, err := manet.WrapNetConn(NewConn(wscon)) if err != nil { wscon.Close() return nil, err diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index 2c9ad32441..b9c688dc4b 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -75,8 +75,14 @@ func TestWebsocketListen(t *testing.T) { return } - c.Write(msg) - c.Close() + _, err = c.Write(msg) + if err != nil { + t.Error(err) + } + err = c.Close() + if err != nil { + t.Error(err) + } }() c, err := l.Accept() @@ -120,8 +126,12 @@ func TestConcurrentClose(t *testing.T) { return } - go c.Write(msg) - go c.Close() + go func() { + _, _ = c.Write(msg) + }() + go func() { + _ = c.Close() + }() } }() From b7f9522391acbd230f97b682c09c5de64edb3632 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 21 May 2019 19:22:52 -0700 Subject: [PATCH 32/93] dep: import go-smux-* into the libp2p org --- p2p/transport/websocket/websocket_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index b9c688dc4b..ae55afe907 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -9,10 +9,10 @@ import ( "testing/iotest" insecure "github.com/libp2p/go-conn-security/insecure" + mplex "github.com/libp2p/go-libp2p-mplex" tptu "github.com/libp2p/go-libp2p-transport-upgrader" utils "github.com/libp2p/go-libp2p-transport/test" ma "github.com/multiformats/go-multiaddr" - mplex "github.com/whyrusleeping/go-smux-multiplex" ) func TestCanDial(t *testing.T) { From a2d70a7084d2209f957ff33d5315b10a13dde7e1 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 24 May 2019 19:01:04 -0700 Subject: [PATCH 33/93] fix: don't try to write an error _twice_. The websocket upgrader does this for us. --- p2p/transport/websocket/listener.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go index 437be9893f..517c719aac 100644 --- a/p2p/transport/websocket/listener.go +++ b/p2p/transport/websocket/listener.go @@ -26,7 +26,7 @@ func (l *listener) serve() { func (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { c, err := upgrader.Upgrade(w, r, nil) if err != nil { - http.Error(w, "Failed to upgrade websocket", 400) + // The upgrader writes a response for us. return } From 3255249d4ea91a5464ccb1bf358bb6273abbf8cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Sun, 26 May 2019 17:34:01 +0100 Subject: [PATCH 34/93] migrate to consolidated types. (#45) --- p2p/transport/websocket/websocket.go | 14 ++++++++------ p2p/transport/websocket/websocket_test.go | 7 ++++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 681d67640c..972681a648 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -8,10 +8,12 @@ import ( "net/http" "net/url" - ws "github.com/gorilla/websocket" - peer "github.com/libp2p/go-libp2p-peer" - tpt "github.com/libp2p/go-libp2p-transport" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/transport" + tptu "github.com/libp2p/go-libp2p-transport-upgrader" + + ws "github.com/gorilla/websocket" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" mafmt "github.com/whyrusleeping/mafmt" @@ -61,7 +63,7 @@ func New(u *tptu.Upgrader) *WebsocketTransport { return &WebsocketTransport{u} } -var _ tpt.Transport = (*WebsocketTransport)(nil) +var _ transport.Transport = (*WebsocketTransport)(nil) func (t *WebsocketTransport) CanDial(a ma.Multiaddr) bool { return WsFmt.Matches(a) @@ -94,7 +96,7 @@ func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (ma return mnc, nil } -func (t *WebsocketTransport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tpt.Conn, error) { +func (t *WebsocketTransport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (transport.CapableConn, error) { macon, err := t.maDial(ctx, raddr) if err != nil { return nil, err @@ -130,7 +132,7 @@ func (t *WebsocketTransport) maListen(a ma.Multiaddr) (manet.Listener, error) { return malist, nil } -func (t *WebsocketTransport) Listen(a ma.Multiaddr) (tpt.Listener, error) { +func (t *WebsocketTransport) Listen(a ma.Multiaddr) (transport.Listener, error) { malist, err := t.maListen(a) if err != nil { return nil, err diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index ae55afe907..4f397c8243 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -8,10 +8,11 @@ import ( "testing" "testing/iotest" - insecure "github.com/libp2p/go-conn-security/insecure" + "github.com/libp2p/go-libp2p-core/sec/insecure" + mplex "github.com/libp2p/go-libp2p-mplex" + ttransport "github.com/libp2p/go-libp2p-testing/suites/transport" tptu "github.com/libp2p/go-libp2p-transport-upgrader" - utils "github.com/libp2p/go-libp2p-transport/test" ma "github.com/multiformats/go-multiaddr" ) @@ -50,7 +51,7 @@ func TestWebsocketTransport(t *testing.T) { }) zero := "/ip4/127.0.0.1/tcp/0/ws" - utils.SubtestTransport(t, ta, tb, zero, "peerA") + ttransport.SubtestTransport(t, ta, tb, zero, "peerA") } func TestWebsocketListen(t *testing.T) { From 7b972a034cfc4cbd5ebac3794d76e9c7ded8e228 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 18 Sep 2019 18:12:23 -0700 Subject: [PATCH 35/93] move multiaddr protocol definitions to go-multiaddr That way, users don't need to import the transport just to parse a websocket multiaddr. --- p2p/transport/websocket/websocket.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 972681a648..75c87f46d8 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -3,7 +3,6 @@ package websocket import ( "context" - "fmt" "net" "net/http" "net/url" @@ -20,11 +19,9 @@ import ( ) // WsProtocol is the multiaddr protocol definition for this transport. -var WsProtocol = ma.Protocol{ - Code: 477, - Name: "ws", - VCode: ma.CodeToVarint(477), -} +// +// Deprecated: use `ma.ProtocolWithCode(ma.P_WS) +var WsProtocol = ma.ProtocolWithCode(ma.P_WS) // WsFmt is multiaddr formatter for WsProtocol var WsFmt = mafmt.And(mafmt.TCP, mafmt.Base(WsProtocol.Code)) @@ -46,11 +43,6 @@ var upgrader = ws.Upgrader{ } func init() { - err := ma.AddProtocol(WsProtocol) - if err != nil { - panic(fmt.Errorf("error registering websocket protocol: %s", err)) - } - manet.RegisterNetCodec(WsCodec) } From 6de8743b2b88565b989cf620e185b10cb563c144 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 18 Sep 2019 18:41:46 -0700 Subject: [PATCH 36/93] dep: update and switch to go-multiaddr-fmt --- p2p/transport/websocket/websocket.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 75c87f46d8..941edac6b8 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -14,8 +14,8 @@ import ( ws "github.com/gorilla/websocket" ma "github.com/multiformats/go-multiaddr" + mafmt "github.com/multiformats/go-multiaddr-fmt" manet "github.com/multiformats/go-multiaddr-net" - mafmt "github.com/whyrusleeping/mafmt" ) // WsProtocol is the multiaddr protocol definition for this transport. From 7f87ef83e4234117e95479ee510fdbbe1852940a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 26 Sep 2019 10:07:42 -0700 Subject: [PATCH 37/93] fix: close gracefully If we send no message, the client gets a 1005. This will send a 1000. --- p2p/transport/websocket/conn.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/p2p/transport/websocket/conn.go b/p2p/transport/websocket/conn.go index b63a28a9ed..d7ae0c194c 100644 --- a/p2p/transport/websocket/conn.go +++ b/p2p/transport/websocket/conn.go @@ -84,7 +84,11 @@ func (c *Conn) Write(b []byte) (n int, err error) { func (c *Conn) Close() error { var err error c.closeOnce.Do(func() { - err1 := c.Conn.WriteControl(ws.CloseMessage, nil, time.Now().Add(GracefulCloseTimeout)) + err1 := c.Conn.WriteControl( + ws.CloseMessage, + ws.FormatCloseMessage(ws.CloseNormalClosure, "closed"), + time.Now().Add(GracefulCloseTimeout), + ) err2 := c.Conn.Close() switch { case err1 != nil: From a7676729b15f27dc42b281d611ea668e40dcc9fc Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 7 Aug 2019 18:34:15 -0700 Subject: [PATCH 38/93] Implement bare bones Wasm support (needs to be cleaned up) --- .../websocket/browser_integration_go_test.go | 80 +++++++ .../websocket/browser_integration_js_test.go | 62 +++++ p2p/transport/websocket/conn.go | 121 ---------- p2p/transport/websocket/conn_go.go | 129 +++++++++++ p2p/transport/websocket/conn_js.go | 212 ++++++++++++++++++ p2p/transport/websocket/listener.go | 2 + p2p/transport/websocket/websocket.go | 92 +------- p2p/transport/websocket/websocket_go.go | 97 ++++++++ p2p/transport/websocket/websocket_js.go | 43 ++++ p2p/transport/websocket/websocket_test.go | 2 + 10 files changed, 629 insertions(+), 211 deletions(-) create mode 100644 p2p/transport/websocket/browser_integration_go_test.go create mode 100644 p2p/transport/websocket/browser_integration_js_test.go create mode 100644 p2p/transport/websocket/conn_go.go create mode 100644 p2p/transport/websocket/conn_js.go create mode 100644 p2p/transport/websocket/websocket_go.go create mode 100644 p2p/transport/websocket/websocket_js.go diff --git a/p2p/transport/websocket/browser_integration_go_test.go b/p2p/transport/websocket/browser_integration_go_test.go new file mode 100644 index 0000000000..1b82b4da3d --- /dev/null +++ b/p2p/transport/websocket/browser_integration_go_test.go @@ -0,0 +1,80 @@ +// +build !js + +package websocket + +import ( + "bufio" + "fmt" + "sync" + "testing" + + "github.com/libp2p/go-libp2p-core/sec/insecure" + mplex "github.com/libp2p/go-libp2p-mplex" + tptu "github.com/libp2p/go-libp2p-transport-upgrader" + ma "github.com/multiformats/go-multiaddr" +) + +const ( + testServerPort = ":9714" +) + +// TestInBrowser is a harness that allows us to use `go test` in order to run +// WebAssembly tests in a headless browser. +func TestInBrowser(t *testing.T) { + // Start a transport which the browser peer will dial. + wg := &sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + tpt := New(&tptu.Upgrader{ + Secure: insecure.New("serverPeer"), + Muxer: new(mplex.Transport), + }) + addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") + if err != nil { + t.Fatal(err) + } + listener, err := tpt.Listen(addr) + if err != nil { + t.Fatal(err) + } + fmt.Println("server started listener") + conn, err := listener.Accept() + if err != nil { + t.Fatal(err) + } + defer conn.Close() + fmt.Println("server received conn") + stream, err := conn.OpenStream() + if err != nil { + fmt.Println("Could not open stream:", err.Error()) + t.Fatal(err) + } + defer stream.Close() + fmt.Println("server opened stream") + buf := bufio.NewReader(stream) + if _, err := stream.Write([]byte("ping\n")); err != nil { + t.Fatal(err) + } + fmt.Println("server wrote message") + msg, err := buf.ReadString('\n') + if err != nil { + t.Fatal("could not read pong message:" + err.Error()) + } + fmt.Println("server received message") + expected := "pong\n" + if msg != expected { + t.Fatalf("Received wrong message. Expected %q but got %q", expected, msg) + } + }() + + // fmt.Println("Starting headless browser tests...") + // cmd := exec.Command("go", "test", "-exec", `"$GOPATH/bin/wasmbrowsertest"`, "-run", "TestInBrowser", ".") + // cmd.Env = append(os.Environ(), []string{"GOOS=js", "GOARCH=wasm"}...) + // if output, err := cmd.CombinedOutput(); err != nil { + // t.Log(string(output)) + // t.Fatal(err) + // } + + wg.Wait() +} diff --git a/p2p/transport/websocket/browser_integration_js_test.go b/p2p/transport/websocket/browser_integration_js_test.go new file mode 100644 index 0000000000..5842e305df --- /dev/null +++ b/p2p/transport/websocket/browser_integration_js_test.go @@ -0,0 +1,62 @@ +// +build js,wasm + +package websocket + +import ( + "bufio" + "context" + "fmt" + "testing" + "time" + + "github.com/libp2p/go-libp2p-core/sec/insecure" + mplex "github.com/libp2p/go-libp2p-mplex" + tptu "github.com/libp2p/go-libp2p-transport-upgrader" + ma "github.com/multiformats/go-multiaddr" +) + +func TestInBrowser(t *testing.T) { + tpt := New(&tptu.Upgrader{ + Secure: insecure.New("browserPeer"), + Muxer: new(mplex.Transport), + }) + addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") + if err != nil { + t.Fatal("could not parse multiaddress:" + err.Error()) + } + fmt.Println("parsed addr") + conn, err := tpt.Dial(context.Background(), addr, "serverPeer") + if err != nil { + t.Fatal("could not dial server:" + err.Error()) + } + fmt.Println("dialed server") + defer conn.Close() + + stream, err := conn.AcceptStream() + if err != nil { + t.Fatal("could not accept stream:" + err.Error()) + } + fmt.Println("accepted stream") + defer stream.Close() + + buf := bufio.NewReader(stream) + msg, err := buf.ReadString('\n') + if err != nil { + t.Fatal("could not read ping message:" + err.Error()) + } + fmt.Println("read ping") + expected := "ping\n" + if msg != expected { + t.Fatalf("Received wrong message. Expected %q but got %q", expected, msg) + } + + _, err = stream.Write([]byte("pong\n")) + if err != nil { + t.Fatal("could not write pong message:" + err.Error()) + } + fmt.Println("wrote pong") + + // TODO(albrow): This hack is necessary in order to give the reader time to + // finish. We should find some way to remove it. + time.Sleep(1 * time.Second) +} diff --git a/p2p/transport/websocket/conn.go b/p2p/transport/websocket/conn.go index d7ae0c194c..b100b44598 100644 --- a/p2p/transport/websocket/conn.go +++ b/p2p/transport/websocket/conn.go @@ -1,12 +1,8 @@ package websocket import ( - "io" "net" - "sync" "time" - - ws "github.com/gorilla/websocket" ) // GracefulCloseTimeout is the time to wait trying to gracefully close a @@ -14,120 +10,3 @@ import ( var GracefulCloseTimeout = 100 * time.Millisecond var _ net.Conn = (*Conn)(nil) - -// Conn implements net.Conn interface for gorilla/websocket. -type Conn struct { - *ws.Conn - DefaultMessageType int - reader io.Reader - closeOnce sync.Once -} - -func (c *Conn) Read(b []byte) (int, error) { - if c.reader == nil { - if err := c.prepNextReader(); err != nil { - return 0, err - } - } - - for { - n, err := c.reader.Read(b) - switch err { - case io.EOF: - c.reader = nil - - if n > 0 { - return n, nil - } - - if err := c.prepNextReader(); err != nil { - return 0, err - } - - // explicitly looping - default: - return n, err - } - } -} - -func (c *Conn) prepNextReader() error { - t, r, err := c.Conn.NextReader() - if err != nil { - if wserr, ok := err.(*ws.CloseError); ok { - if wserr.Code == 1000 || wserr.Code == 1005 { - return io.EOF - } - } - return err - } - - if t == ws.CloseMessage { - return io.EOF - } - - c.reader = r - return nil -} - -func (c *Conn) Write(b []byte) (n int, err error) { - if err := c.Conn.WriteMessage(c.DefaultMessageType, b); err != nil { - return 0, err - } - - return len(b), nil -} - -// Close closes the connection. Only the first call to Close will receive the -// close error, subsequent and concurrent calls will return nil. -// This method is thread-safe. -func (c *Conn) Close() error { - var err error - c.closeOnce.Do(func() { - err1 := c.Conn.WriteControl( - ws.CloseMessage, - ws.FormatCloseMessage(ws.CloseNormalClosure, "closed"), - time.Now().Add(GracefulCloseTimeout), - ) - err2 := c.Conn.Close() - switch { - case err1 != nil: - err = err1 - case err2 != nil: - err = err2 - } - }) - return err -} - -func (c *Conn) LocalAddr() net.Addr { - return NewAddr(c.Conn.LocalAddr().String()) -} - -func (c *Conn) RemoteAddr() net.Addr { - return NewAddr(c.Conn.RemoteAddr().String()) -} - -func (c *Conn) SetDeadline(t time.Time) error { - if err := c.SetReadDeadline(t); err != nil { - return err - } - - return c.SetWriteDeadline(t) -} - -func (c *Conn) SetReadDeadline(t time.Time) error { - return c.Conn.SetReadDeadline(t) -} - -func (c *Conn) SetWriteDeadline(t time.Time) error { - return c.Conn.SetWriteDeadline(t) -} - -// NewConn creates a Conn given a regular gorilla/websocket Conn. -func NewConn(raw *ws.Conn) *Conn { - return &Conn{ - Conn: raw, - DefaultMessageType: ws.BinaryMessage, - } -} diff --git a/p2p/transport/websocket/conn_go.go b/p2p/transport/websocket/conn_go.go new file mode 100644 index 0000000000..6be4697ec6 --- /dev/null +++ b/p2p/transport/websocket/conn_go.go @@ -0,0 +1,129 @@ +// +build !js + +package websocket + +import ( + "io" + "net" + "sync" + "time" + + ws "github.com/gorilla/websocket" +) + +// Conn implements net.Conn interface for gorilla/websocket. +type Conn struct { + *ws.Conn + DefaultMessageType int + reader io.Reader + closeOnce sync.Once +} + +func (c *Conn) Read(b []byte) (int, error) { + if c.reader == nil { + if err := c.prepNextReader(); err != nil { + return 0, err + } + } + + for { + n, err := c.reader.Read(b) + switch err { + case io.EOF: + c.reader = nil + + if n > 0 { + return n, nil + } + + if err := c.prepNextReader(); err != nil { + return 0, err + } + + // explicitly looping + default: + return n, err + } + } +} + +func (c *Conn) prepNextReader() error { + t, r, err := c.Conn.NextReader() + if err != nil { + if wserr, ok := err.(*ws.CloseError); ok { + if wserr.Code == 1000 || wserr.Code == 1005 { + return io.EOF + } + } + return err + } + + if t == ws.CloseMessage { + return io.EOF + } + + c.reader = r + return nil +} + +func (c *Conn) Write(b []byte) (n int, err error) { + if err := c.Conn.WriteMessage(c.DefaultMessageType, b); err != nil { + return 0, err + } + + return len(b), nil +} + +// Close closes the connection. Only the first call to Close will receive the +// close error, subsequent and concurrent calls will return nil. +// This method is thread-safe. +func (c *Conn) Close() error { + var err error + c.closeOnce.Do(func() { + err1 := c.Conn.WriteControl( + ws.CloseMessage, + ws.FormatCloseMessage(ws.CloseNormalClosure, "closed"), + time.Now().Add(GracefulCloseTimeout), + ) + err2 := c.Conn.Close() + switch { + case err1 != nil: + err = err1 + case err2 != nil: + err = err2 + } + }) + return err +} + +func (c *Conn) LocalAddr() net.Addr { + return NewAddr(c.Conn.LocalAddr().String()) +} + +func (c *Conn) RemoteAddr() net.Addr { + return NewAddr(c.Conn.RemoteAddr().String()) +} + +func (c *Conn) SetDeadline(t time.Time) error { + if err := c.SetReadDeadline(t); err != nil { + return err + } + + return c.SetWriteDeadline(t) +} + +func (c *Conn) SetReadDeadline(t time.Time) error { + return c.Conn.SetReadDeadline(t) +} + +func (c *Conn) SetWriteDeadline(t time.Time) error { + return c.Conn.SetWriteDeadline(t) +} + +// NewConn creates a Conn given a regular gorilla/websocket Conn. +func NewConn(raw *ws.Conn) *Conn { + return &Conn{ + Conn: raw, + DefaultMessageType: ws.BinaryMessage, + } +} diff --git a/p2p/transport/websocket/conn_js.go b/p2p/transport/websocket/conn_js.go new file mode 100644 index 0000000000..cb8b3248d3 --- /dev/null +++ b/p2p/transport/websocket/conn_js.go @@ -0,0 +1,212 @@ +package websocket + +import ( + "bytes" + "errors" + "fmt" + "io" + "net" + "strings" + "sync" + "syscall/js" + "time" +) + +const ( + webSocketStateConnecting = 0 + webSocketStateOpen = 1 + webSocketStateClosing = 2 + webSocketStateClosed = 3 +) + +var errIsClosed = errors.New("connection is closed") + +// Conn implements net.Conn interface for WebSockets in js/wasm. +type Conn struct { + js.Value + messageHandler *js.Func + closeHandler *js.Func + mut sync.Mutex + incomingData chan []byte + currData *bytes.Buffer + closeSignal chan struct{} +} + +func (c *Conn) Read(b []byte) (int, error) { + c.mut.Lock() + defer c.mut.Unlock() + if err := c.checkOpen(); err != nil { + return 0, io.EOF + } + + if c.currData == nil { + fmt.Println("Waiting for incoming data") + select { + case data := <-c.incomingData: + fmt.Println("Received new incoming data") + fmt.Println(string(data)) + c.currData = bytes.NewBuffer(data) + case <-c.closeSignal: + return 0, io.EOF + } + } + + n, err := c.currData.Read(b) + if err == io.EOF { + c.currData = nil + return n, nil + } + return n, err +} + +func (c *Conn) checkOpen() error { + state := c.Get("readyState").Int() + switch state { + case webSocketStateOpen: + fmt.Println("readyState is open") + case webSocketStateClosed, webSocketStateClosing: + fmt.Println("readyState is closed or closing") + return errIsClosed + default: + fmt.Printf("readyState is %d\n", state) + } + return nil +} + +func (c *Conn) Write(b []byte) (n int, err error) { + fmt.Println("Write was called") + // c.mut.Lock() + // defer c.mut.Unlock() + if err := c.checkOpen(); err != nil { + return 0, err + } + c.Call("send", string(b)) + return len(b), nil +} + +// Close closes the connection. Only the first call to Close will receive the +// close error, subsequent and concurrent calls will return nil. +// This method is thread-safe. +func (c *Conn) Close() error { + // c.mut.Lock() + // defer c.mut.Unlock() + c.Call("close") + if c.messageHandler != nil { + c.Call("removeEventListener", "message", *c.messageHandler) + c.messageHandler.Release() + } + if c.closeHandler != nil { + c.Call("removeEventListener", "close", *c.closeHandler) + c.closeHandler.Release() + } + return nil +} + +func (c *Conn) LocalAddr() net.Addr { + // TODO(albrow): is there some way to get the local address? + return NewAddr("0.0.0.0:0") +} + +func (c *Conn) RemoteAddr() net.Addr { + rawURL := c.Get("url").String() + withoutPrefix := strings.TrimPrefix(rawURL, "ws://") + withoutSuffix := strings.TrimSuffix(withoutPrefix, "/") + return NewAddr(withoutSuffix) +} + +func (c *Conn) SetDeadline(t time.Time) error { + // Not supported + return nil +} + +func (c *Conn) SetReadDeadline(t time.Time) error { + // Not supported + return nil +} + +func (c *Conn) SetWriteDeadline(t time.Time) error { + // Not supported + return nil +} + +// NewConn creates a Conn given a regular js/wasm WebSocket Conn. +func NewConn(raw js.Value) *Conn { + conn := &Conn{ + Value: raw, + incomingData: make(chan []byte), + closeSignal: make(chan struct{}), + } + conn.setUpHandlers() + return conn +} + +func (c *Conn) setUpHandlers() { + // c.mut.Lock() + // defer c.mut.Unlock() + if c.messageHandler != nil { + // Message handlers already created. Nothing to do. + return + } + messageHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + go func() { + // TODO(albrow): Currently we assume data is of type Blob. Really we + // should check binaryType and then decode accordingly. + blob := args[0].Get("data") + text := readBlob(blob) + fmt.Println("onmessage was triggered") + fmt.Println("received:", text) + fmt.Println("Sending to incomingData") + c.incomingData <- []byte(text) + fmt.Println("Sent to incomingData") + }() + return nil + }) + c.messageHandler = &messageHandler + c.Call("addEventListener", "message", messageHandler) + + closeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + fmt.Println("onclose was triggered") + close(c.closeSignal) + return nil + }) + c.closeHandler = &closeHandler + c.Call("addEventListener", "close", closeHandler) +} + +func (c *Conn) waitForOpen() error { + openSignal := make(chan struct{}) + handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + fmt.Println("onopen was triggered") + close(openSignal) + return nil + }) + defer func() { + c.Call("removeEventListener", "open", handler) + }() + defer handler.Release() + c.Call("addEventListener", "open", handler) + <-openSignal + return nil +} + +func readBlob(blob js.Value) string { + // const reader = new FileReader(); + // reader.addEventListener('loadend', (e) => { + // const text = e.srcElement.result; + // console.log(text); + // }); + // reader.readAsText(blb); + reader := js.Global().Get("FileReader").New() + textChan := make(chan string) + loadEndFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + go func() { + text := args[0].Get("srcElement").Get("result").String() + textChan <- text + }() + return nil + }) + defer loadEndFunc.Release() + reader.Call("addEventListener", "loadend", loadEndFunc) + reader.Call("readAsText", blob) + return <-textChan +} diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go index 517c719aac..47fb8e7129 100644 --- a/p2p/transport/websocket/listener.go +++ b/p2p/transport/websocket/listener.go @@ -1,3 +1,5 @@ +// +build !js + package websocket import ( diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 941edac6b8..7e1c900f27 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -3,16 +3,10 @@ package websocket import ( "context" - "net" - "net/http" - "net/url" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/transport" - tptu "github.com/libp2p/go-libp2p-transport-upgrader" - - ws "github.com/gorilla/websocket" ma "github.com/multiformats/go-multiaddr" mafmt "github.com/multiformats/go-multiaddr-fmt" manet "github.com/multiformats/go-multiaddr-net" @@ -34,18 +28,12 @@ var WsCodec = &manet.NetCodec{ ParseNetAddr: ParseWebsocketNetAddr, } -// Default gorilla upgrader -var upgrader = ws.Upgrader{ - // Allow requests from *all* origins. - CheckOrigin: func(r *http.Request) bool { - return true - }, -} - func init() { manet.RegisterNetCodec(WsCodec) } +var _ transport.Transport = (*WebsocketTransport)(nil) + // WebsocketTransport is the actual go-libp2p transport type WebsocketTransport struct { Upgrader *tptu.Upgrader @@ -55,8 +43,6 @@ func New(u *tptu.Upgrader) *WebsocketTransport { return &WebsocketTransport{u} } -var _ transport.Transport = (*WebsocketTransport)(nil) - func (t *WebsocketTransport) CanDial(a ma.Multiaddr) bool { return WsFmt.Matches(a) } @@ -69,25 +55,6 @@ func (t *WebsocketTransport) Proxy() bool { return false } -func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) { - wsurl, err := parseMultiaddr(raddr) - if err != nil { - return nil, err - } - - wscon, _, err := ws.DefaultDialer.Dial(wsurl, nil) - if err != nil { - return nil, err - } - - mnc, err := manet.WrapNetConn(NewConn(wscon)) - if err != nil { - wscon.Close() - return nil, err - } - return mnc, nil -} - func (t *WebsocketTransport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (transport.CapableConn, error) { macon, err := t.maDial(ctx, raddr) if err != nil { @@ -95,58 +62,3 @@ func (t *WebsocketTransport) Dial(ctx context.Context, raddr ma.Multiaddr, p pee } return t.Upgrader.UpgradeOutbound(ctx, t, macon, p) } - -func (t *WebsocketTransport) maListen(a ma.Multiaddr) (manet.Listener, error) { - lnet, lnaddr, err := manet.DialArgs(a) - if err != nil { - return nil, err - } - - nl, err := net.Listen(lnet, lnaddr) - if err != nil { - return nil, err - } - - u, err := url.Parse("http://" + nl.Addr().String()) - if err != nil { - nl.Close() - return nil, err - } - - malist, err := t.wrapListener(nl, u) - if err != nil { - nl.Close() - return nil, err - } - - go malist.serve() - - return malist, nil -} - -func (t *WebsocketTransport) Listen(a ma.Multiaddr) (transport.Listener, error) { - malist, err := t.maListen(a) - if err != nil { - return nil, err - } - return t.Upgrader.UpgradeListener(t, malist), nil -} - -func (t *WebsocketTransport) wrapListener(l net.Listener, origin *url.URL) (*listener, error) { - laddr, err := manet.FromNetAddr(l.Addr()) - if err != nil { - return nil, err - } - wsma, err := ma.NewMultiaddr("/ws") - if err != nil { - return nil, err - } - laddr = laddr.Encapsulate(wsma) - - return &listener{ - laddr: laddr, - Listener: l, - incoming: make(chan *Conn), - closed: make(chan struct{}), - }, nil -} diff --git a/p2p/transport/websocket/websocket_go.go b/p2p/transport/websocket/websocket_go.go new file mode 100644 index 0000000000..86b872c02f --- /dev/null +++ b/p2p/transport/websocket/websocket_go.go @@ -0,0 +1,97 @@ +// +build !js + +package websocket + +import ( + "context" + "net" + "net/http" + "net/url" + + ws "github.com/gorilla/websocket" + "github.com/libp2p/go-libp2p-core/transport" + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" +) + +// Default gorilla upgrader +var upgrader = ws.Upgrader{ + // Allow requests from *all* origins. + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) { + wsurl, err := parseMultiaddr(raddr) + if err != nil { + return nil, err + } + + wscon, _, err := ws.DefaultDialer.Dial(wsurl, nil) + if err != nil { + return nil, err + } + + mnc, err := manet.WrapNetConn(NewConn(wscon)) + if err != nil { + wscon.Close() + return nil, err + } + return mnc, nil +} + +func (t *WebsocketTransport) maListen(a ma.Multiaddr) (manet.Listener, error) { + lnet, lnaddr, err := manet.DialArgs(a) + if err != nil { + return nil, err + } + + nl, err := net.Listen(lnet, lnaddr) + if err != nil { + return nil, err + } + + u, err := url.Parse("http://" + nl.Addr().String()) + if err != nil { + nl.Close() + return nil, err + } + + malist, err := t.wrapListener(nl, u) + if err != nil { + nl.Close() + return nil, err + } + + go malist.serve() + + return malist, nil +} + +func (t *WebsocketTransport) Listen(a ma.Multiaddr) (transport.Listener, error) { + malist, err := t.maListen(a) + if err != nil { + return nil, err + } + return t.Upgrader.UpgradeListener(t, malist), nil +} + +func (t *WebsocketTransport) wrapListener(l net.Listener, origin *url.URL) (*listener, error) { + laddr, err := manet.FromNetAddr(l.Addr()) + if err != nil { + return nil, err + } + wsma, err := ma.NewMultiaddr("/ws") + if err != nil { + return nil, err + } + laddr = laddr.Encapsulate(wsma) + + return &listener{ + laddr: laddr, + Listener: l, + incoming: make(chan *Conn), + closed: make(chan struct{}), + }, nil +} diff --git a/p2p/transport/websocket/websocket_js.go b/p2p/transport/websocket/websocket_js.go new file mode 100644 index 0000000000..fd3c812490 --- /dev/null +++ b/p2p/transport/websocket/websocket_js.go @@ -0,0 +1,43 @@ +// +build js,wasm + +package websocket + +import ( + "context" + "errors" + "fmt" + "syscall/js" + + "github.com/libp2p/go-libp2p-core/transport" + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" +) + +func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) { + wsurl, err := parseMultiaddr(raddr) + if err != nil { + return nil, err + } + fmt.Println("wsurl:", wsurl) + + rawConn := js.Global().Get("WebSocket").New(wsurl) + rawConn.Call("addEventListener", "error", js.FuncOf(func(this js.Value, args []js.Value) interface{} { + fmt.Println("onerror was triggered") + js.Global().Get("console").Call("log", args[0]) + return nil + })) + conn := NewConn(rawConn) + conn.waitForOpen() + mnc, err := manet.WrapNetConn(conn) + if err != nil { + conn.Close() + return nil, err + } + + fmt.Println("Returning from maDial") + return mnc, nil +} + +func (t *WebsocketTransport) Listen(a ma.Multiaddr) (transport.Listener, error) { + return nil, errors.New("Listen not implemented on js/wasm") +} diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index 4f397c8243..30872e94cd 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -1,3 +1,5 @@ +// +build !js + package websocket import ( From 486168bb29acf3e15eb0b7522b2b1b6e7351dea4 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Thu, 8 Aug 2019 15:04:58 -0700 Subject: [PATCH 39/93] Make it possible to run browser tests with a single go test command --- .../websocket/browser_integration_go_test.go | 50 +++++++++---------- .../websocket/browser_integration_js_test.go | 6 --- p2p/transport/websocket/conn_js.go | 16 ------ p2p/transport/websocket/websocket_js.go | 4 -- p2p/transport/websocket/websocket_test.go | 1 - 5 files changed, 25 insertions(+), 52 deletions(-) diff --git a/p2p/transport/websocket/browser_integration_go_test.go b/p2p/transport/websocket/browser_integration_go_test.go index 1b82b4da3d..464fca73a8 100644 --- a/p2p/transport/websocket/browser_integration_go_test.go +++ b/p2p/transport/websocket/browser_integration_go_test.go @@ -5,7 +5,10 @@ package websocket import ( "bufio" "fmt" - "sync" + "os" + "os/exec" + "path/filepath" + "strings" "testing" "github.com/libp2p/go-libp2p-core/sec/insecure" @@ -22,59 +25,56 @@ const ( // WebAssembly tests in a headless browser. func TestInBrowser(t *testing.T) { // Start a transport which the browser peer will dial. - wg := &sync.WaitGroup{} - wg.Add(1) + serverDoneSignal := make(chan struct{}) go func() { - defer wg.Done() + defer func() { + close(serverDoneSignal) + }() tpt := New(&tptu.Upgrader{ Secure: insecure.New("serverPeer"), Muxer: new(mplex.Transport), }) addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") if err != nil { - t.Fatal(err) + t.Fatal("SERVER:", err) } listener, err := tpt.Listen(addr) if err != nil { - t.Fatal(err) + t.Fatal("SERVER:", err) } - fmt.Println("server started listener") conn, err := listener.Accept() if err != nil { - t.Fatal(err) + t.Fatal("SERVER:", err) } defer conn.Close() - fmt.Println("server received conn") stream, err := conn.OpenStream() if err != nil { - fmt.Println("Could not open stream:", err.Error()) - t.Fatal(err) + t.Fatal("SERVER: could not open stream:", err) } defer stream.Close() - fmt.Println("server opened stream") buf := bufio.NewReader(stream) if _, err := stream.Write([]byte("ping\n")); err != nil { - t.Fatal(err) + t.Fatal("SERVER:", err) } - fmt.Println("server wrote message") msg, err := buf.ReadString('\n') if err != nil { - t.Fatal("could not read pong message:" + err.Error()) + t.Fatal("SERVER: could not read pong message:" + err.Error()) } - fmt.Println("server received message") expected := "pong\n" if msg != expected { - t.Fatalf("Received wrong message. Expected %q but got %q", expected, msg) + t.Fatalf("SERVER: Received wrong message. Expected %q but got %q", expected, msg) } }() - // fmt.Println("Starting headless browser tests...") - // cmd := exec.Command("go", "test", "-exec", `"$GOPATH/bin/wasmbrowsertest"`, "-run", "TestInBrowser", ".") - // cmd.Env = append(os.Environ(), []string{"GOOS=js", "GOARCH=wasm"}...) - // if output, err := cmd.CombinedOutput(); err != nil { - // t.Log(string(output)) - // t.Fatal(err) - // } + testExecPath := filepath.Join(os.Getenv("GOPATH"), "bin", "wasmbrowsertest") + cmd := exec.Command("go", "test", "-exec", testExecPath, "-run", "TestInBrowser", ".", "-v") + cmd.Env = append(os.Environ(), []string{"GOOS=js", "GOARCH=wasm"}...) + output, err := cmd.CombinedOutput() + if err != nil { + formattedOutput := "\t" + strings.Join(strings.Split(string(output), "\n"), "\n\t") + fmt.Println("BROWSER OUTPUT:\n", formattedOutput) + t.Fatal("BROWSER:", err) + } - wg.Wait() + <-serverDoneSignal } diff --git a/p2p/transport/websocket/browser_integration_js_test.go b/p2p/transport/websocket/browser_integration_js_test.go index 5842e305df..656db447ba 100644 --- a/p2p/transport/websocket/browser_integration_js_test.go +++ b/p2p/transport/websocket/browser_integration_js_test.go @@ -5,7 +5,6 @@ package websocket import ( "bufio" "context" - "fmt" "testing" "time" @@ -24,19 +23,16 @@ func TestInBrowser(t *testing.T) { if err != nil { t.Fatal("could not parse multiaddress:" + err.Error()) } - fmt.Println("parsed addr") conn, err := tpt.Dial(context.Background(), addr, "serverPeer") if err != nil { t.Fatal("could not dial server:" + err.Error()) } - fmt.Println("dialed server") defer conn.Close() stream, err := conn.AcceptStream() if err != nil { t.Fatal("could not accept stream:" + err.Error()) } - fmt.Println("accepted stream") defer stream.Close() buf := bufio.NewReader(stream) @@ -44,7 +40,6 @@ func TestInBrowser(t *testing.T) { if err != nil { t.Fatal("could not read ping message:" + err.Error()) } - fmt.Println("read ping") expected := "ping\n" if msg != expected { t.Fatalf("Received wrong message. Expected %q but got %q", expected, msg) @@ -54,7 +49,6 @@ func TestInBrowser(t *testing.T) { if err != nil { t.Fatal("could not write pong message:" + err.Error()) } - fmt.Println("wrote pong") // TODO(albrow): This hack is necessary in order to give the reader time to // finish. We should find some way to remove it. diff --git a/p2p/transport/websocket/conn_js.go b/p2p/transport/websocket/conn_js.go index cb8b3248d3..bd04f83772 100644 --- a/p2p/transport/websocket/conn_js.go +++ b/p2p/transport/websocket/conn_js.go @@ -3,7 +3,6 @@ package websocket import ( "bytes" "errors" - "fmt" "io" "net" "strings" @@ -40,11 +39,8 @@ func (c *Conn) Read(b []byte) (int, error) { } if c.currData == nil { - fmt.Println("Waiting for incoming data") select { case data := <-c.incomingData: - fmt.Println("Received new incoming data") - fmt.Println(string(data)) c.currData = bytes.NewBuffer(data) case <-c.closeSignal: return 0, io.EOF @@ -62,19 +58,13 @@ func (c *Conn) Read(b []byte) (int, error) { func (c *Conn) checkOpen() error { state := c.Get("readyState").Int() switch state { - case webSocketStateOpen: - fmt.Println("readyState is open") case webSocketStateClosed, webSocketStateClosing: - fmt.Println("readyState is closed or closing") return errIsClosed - default: - fmt.Printf("readyState is %d\n", state) } return nil } func (c *Conn) Write(b []byte) (n int, err error) { - fmt.Println("Write was called") // c.mut.Lock() // defer c.mut.Unlock() if err := c.checkOpen(); err != nil { @@ -153,11 +143,7 @@ func (c *Conn) setUpHandlers() { // should check binaryType and then decode accordingly. blob := args[0].Get("data") text := readBlob(blob) - fmt.Println("onmessage was triggered") - fmt.Println("received:", text) - fmt.Println("Sending to incomingData") c.incomingData <- []byte(text) - fmt.Println("Sent to incomingData") }() return nil }) @@ -165,7 +151,6 @@ func (c *Conn) setUpHandlers() { c.Call("addEventListener", "message", messageHandler) closeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - fmt.Println("onclose was triggered") close(c.closeSignal) return nil }) @@ -176,7 +161,6 @@ func (c *Conn) setUpHandlers() { func (c *Conn) waitForOpen() error { openSignal := make(chan struct{}) handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - fmt.Println("onopen was triggered") close(openSignal) return nil }) diff --git a/p2p/transport/websocket/websocket_js.go b/p2p/transport/websocket/websocket_js.go index fd3c812490..9002533e0c 100644 --- a/p2p/transport/websocket/websocket_js.go +++ b/p2p/transport/websocket/websocket_js.go @@ -5,7 +5,6 @@ package websocket import ( "context" "errors" - "fmt" "syscall/js" "github.com/libp2p/go-libp2p-core/transport" @@ -18,11 +17,9 @@ func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (ma if err != nil { return nil, err } - fmt.Println("wsurl:", wsurl) rawConn := js.Global().Get("WebSocket").New(wsurl) rawConn.Call("addEventListener", "error", js.FuncOf(func(this js.Value, args []js.Value) interface{} { - fmt.Println("onerror was triggered") js.Global().Get("console").Call("log", args[0]) return nil })) @@ -34,7 +31,6 @@ func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (ma return nil, err } - fmt.Println("Returning from maDial") return mnc, nil } diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index 30872e94cd..bada345232 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -11,7 +11,6 @@ import ( "testing/iotest" "github.com/libp2p/go-libp2p-core/sec/insecure" - mplex "github.com/libp2p/go-libp2p-mplex" ttransport "github.com/libp2p/go-libp2p-testing/suites/transport" tptu "github.com/libp2p/go-libp2p-transport-upgrader" From 4979f94d4e97b06f51c8c86e1bd8f5bddd0f0807 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 14 Aug 2019 15:42:56 -0700 Subject: [PATCH 40/93] Guarantee reading happens in order and add some comments --- p2p/transport/websocket/conn_js.go | 146 ++++++++++++++++++----------- 1 file changed, 90 insertions(+), 56 deletions(-) diff --git a/p2p/transport/websocket/conn_js.go b/p2p/transport/websocket/conn_js.go index bd04f83772..9bca3f3518 100644 --- a/p2p/transport/websocket/conn_js.go +++ b/p2p/transport/websocket/conn_js.go @@ -18,7 +18,9 @@ const ( webSocketStateClosed = 3 ) -var errIsClosed = errors.New("connection is closed") +const incomingDataBufferSize = 100 + +var errConnectionClosed = errors.New("connection is closed") // Conn implements net.Conn interface for WebSockets in js/wasm. type Conn struct { @@ -27,50 +29,78 @@ type Conn struct { closeHandler *js.Func mut sync.Mutex incomingData chan []byte - currData *bytes.Buffer + currDataMut sync.RWMutex + currData bytes.Buffer closeSignal chan struct{} + dataSignal chan struct{} +} + +// NewConn creates a Conn given a regular js/wasm WebSocket Conn. +func NewConn(raw js.Value) *Conn { + conn := &Conn{ + Value: raw, + incomingData: make(chan []byte, incomingDataBufferSize), + closeSignal: make(chan struct{}), + dataSignal: make(chan struct{}), + } + conn.setUpHandlers() + go func() { + // TODO(albrow): Handle error appropriately + err := conn.readLoop() + if err != nil { + panic(err) + } + }() + return conn } func (c *Conn) Read(b []byte) (int, error) { - c.mut.Lock() - defer c.mut.Unlock() if err := c.checkOpen(); err != nil { return 0, io.EOF } - if c.currData == nil { - select { - case data := <-c.incomingData: - c.currData = bytes.NewBuffer(data) - case <-c.closeSignal: - return 0, io.EOF + for { + c.currDataMut.RLock() + n, err := c.currData.Read(b) + c.currDataMut.RUnlock() + if err != nil && err != io.EOF { + // Return any unexpected errors immediately. + return n, err + } else if n == 0 || err == io.EOF { + // There is no data ready to be read. Wait for more data or for the + // connection to be closed. + select { + case <-c.dataSignal: + continue + case <-c.closeSignal: + return 0, io.EOF + } + } else { + return n, err } } - - n, err := c.currData.Read(b) - if err == io.EOF { - c.currData = nil - return n, nil - } - return n, err } +// checkOpen returns an error if the connection is not open. Otherwise, it +// returns nil. func (c *Conn) checkOpen() error { state := c.Get("readyState").Int() switch state { case webSocketStateClosed, webSocketStateClosing: - return errIsClosed + return errConnectionClosed } return nil } func (c *Conn) Write(b []byte) (n int, err error) { - // c.mut.Lock() - // defer c.mut.Unlock() if err := c.checkOpen(); err != nil { return 0, err } - c.Call("send", string(b)) + uint8Array := js.Global().Get("Uint8Array").New(len(b)) + for i := 0; i < len(b); i++ { + uint8Array.SetIndex(i, b[i]) + } + c.Call("send", uint8Array.Get("buffer")) return len(b), nil } @@ -78,8 +108,8 @@ func (c *Conn) Write(b []byte) (n int, err error) { // close error, subsequent and concurrent calls will return nil. // This method is thread-safe. func (c *Conn) Close() error { - // c.mut.Lock() - // defer c.mut.Unlock() + c.mut.Lock() + defer c.mut.Unlock() c.Call("close") if c.messageHandler != nil { c.Call("removeEventListener", "message", *c.messageHandler) @@ -93,7 +123,6 @@ func (c *Conn) Close() error { } func (c *Conn) LocalAddr() net.Addr { - // TODO(albrow): is there some way to get the local address? return NewAddr("0.0.0.0:0") } @@ -119,20 +148,9 @@ func (c *Conn) SetWriteDeadline(t time.Time) error { return nil } -// NewConn creates a Conn given a regular js/wasm WebSocket Conn. -func NewConn(raw js.Value) *Conn { - conn := &Conn{ - Value: raw, - incomingData: make(chan []byte), - closeSignal: make(chan struct{}), - } - conn.setUpHandlers() - return conn -} - func (c *Conn) setUpHandlers() { - // c.mut.Lock() - // defer c.mut.Unlock() + c.mut.Lock() + defer c.mut.Unlock() if c.messageHandler != nil { // Message handlers already created. Nothing to do. return @@ -142,8 +160,8 @@ func (c *Conn) setUpHandlers() { // TODO(albrow): Currently we assume data is of type Blob. Really we // should check binaryType and then decode accordingly. blob := args[0].Get("data") - text := readBlob(blob) - c.incomingData <- []byte(text) + data := readBlob(blob) + c.incomingData <- data }() return nil }) @@ -158,39 +176,55 @@ func (c *Conn) setUpHandlers() { c.Call("addEventListener", "close", closeHandler) } +// readLoop continuosly reads from the c.incoming data channel and writes to the +// current data buffer. +func (c *Conn) readLoop() error { + for data := range c.incomingData { + c.currDataMut.Lock() + _, err := c.currData.Write(data) + if err != nil { + c.currDataMut.Unlock() + return err + } + c.currDataMut.Unlock() + c.dataSignal <- struct{}{} + } + return nil +} + func (c *Conn) waitForOpen() error { openSignal := make(chan struct{}) handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { close(openSignal) return nil }) - defer func() { - c.Call("removeEventListener", "open", handler) - }() + defer c.Call("removeEventListener", "open", handler) defer handler.Release() c.Call("addEventListener", "open", handler) <-openSignal return nil } -func readBlob(blob js.Value) string { - // const reader = new FileReader(); - // reader.addEventListener('loadend', (e) => { - // const text = e.srcElement.result; - // console.log(text); - // }); - // reader.readAsText(blb); +// readBlob converts a JavaScript Blob into a slice of bytes. It uses the +// FileReader API under the hood. +func readBlob(blob js.Value) []byte { reader := js.Global().Get("FileReader").New() - textChan := make(chan string) + dataChan := make(chan []byte) loadEndFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - go func() { - text := args[0].Get("srcElement").Get("result").String() - textChan <- text - }() + // event.result is of type ArrayBuffer. We can convert it to a JavaScript + // Uint8Array, which is directly translatable to the Go type []byte. + buffer := reader.Get("result") + view := js.Global().Get("Uint8Array").New(buffer) + dataLen := view.Get("length").Int() + data := make([]byte, dataLen) + for i := 0; i < dataLen; i++ { + data[i] = byte(view.Index(i).Int()) + } + dataChan <- data return nil }) defer loadEndFunc.Release() reader.Call("addEventListener", "loadend", loadEndFunc) - reader.Call("readAsText", blob) - return <-textChan + reader.Call("readAsArrayBuffer", blob) + return <-dataChan } From ee5c8299d28ecfd7f8b9aff86e26469b082f7642 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 14 Aug 2019 15:49:47 -0700 Subject: [PATCH 41/93] return os.ErrNoDeadline where appropriate --- p2p/transport/websocket/conn_js.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/p2p/transport/websocket/conn_js.go b/p2p/transport/websocket/conn_js.go index 9bca3f3518..d8c8a9cdfb 100644 --- a/p2p/transport/websocket/conn_js.go +++ b/p2p/transport/websocket/conn_js.go @@ -5,6 +5,7 @@ import ( "errors" "io" "net" + "os" "strings" "sync" "syscall/js" @@ -134,18 +135,15 @@ func (c *Conn) RemoteAddr() net.Addr { } func (c *Conn) SetDeadline(t time.Time) error { - // Not supported - return nil + return os.ErrNoDeadline } func (c *Conn) SetReadDeadline(t time.Time) error { - // Not supported - return nil + return os.ErrNoDeadline } func (c *Conn) SetWriteDeadline(t time.Time) error { - // Not supported - return nil + return os.ErrNoDeadline } func (c *Conn) setUpHandlers() { From 31e5ba48ed86523337fb0da924391d5c2be45b8e Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 14 Aug 2019 15:57:36 -0700 Subject: [PATCH 42/93] Change go and js suffixes to native and browser --- ...tegration_js_test.go => browser_integration_browser_test.go} | 0 ...ntegration_go_test.go => browser_integration_native_test.go} | 0 p2p/transport/websocket/{conn_js.go => conn_browser.go} | 2 ++ p2p/transport/websocket/{conn_go.go => conn_native.go} | 0 .../websocket/{websocket_js.go => websocket_browser.go} | 0 .../websocket/{websocket_go.go => websocket_native.go} | 0 6 files changed, 2 insertions(+) rename p2p/transport/websocket/{browser_integration_js_test.go => browser_integration_browser_test.go} (100%) rename p2p/transport/websocket/{browser_integration_go_test.go => browser_integration_native_test.go} (100%) rename p2p/transport/websocket/{conn_js.go => conn_browser.go} (99%) rename p2p/transport/websocket/{conn_go.go => conn_native.go} (100%) rename p2p/transport/websocket/{websocket_js.go => websocket_browser.go} (100%) rename p2p/transport/websocket/{websocket_go.go => websocket_native.go} (100%) diff --git a/p2p/transport/websocket/browser_integration_js_test.go b/p2p/transport/websocket/browser_integration_browser_test.go similarity index 100% rename from p2p/transport/websocket/browser_integration_js_test.go rename to p2p/transport/websocket/browser_integration_browser_test.go diff --git a/p2p/transport/websocket/browser_integration_go_test.go b/p2p/transport/websocket/browser_integration_native_test.go similarity index 100% rename from p2p/transport/websocket/browser_integration_go_test.go rename to p2p/transport/websocket/browser_integration_native_test.go diff --git a/p2p/transport/websocket/conn_js.go b/p2p/transport/websocket/conn_browser.go similarity index 99% rename from p2p/transport/websocket/conn_js.go rename to p2p/transport/websocket/conn_browser.go index d8c8a9cdfb..76a51f6c39 100644 --- a/p2p/transport/websocket/conn_js.go +++ b/p2p/transport/websocket/conn_browser.go @@ -1,3 +1,5 @@ +// +build js,wasm + package websocket import ( diff --git a/p2p/transport/websocket/conn_go.go b/p2p/transport/websocket/conn_native.go similarity index 100% rename from p2p/transport/websocket/conn_go.go rename to p2p/transport/websocket/conn_native.go diff --git a/p2p/transport/websocket/websocket_js.go b/p2p/transport/websocket/websocket_browser.go similarity index 100% rename from p2p/transport/websocket/websocket_js.go rename to p2p/transport/websocket/websocket_browser.go diff --git a/p2p/transport/websocket/websocket_go.go b/p2p/transport/websocket/websocket_native.go similarity index 100% rename from p2p/transport/websocket/websocket_go.go rename to p2p/transport/websocket/websocket_native.go From a52e415ef70b8a6c4eed3a59c0d95f9747e398e5 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Fri, 16 Aug 2019 17:40:31 -0700 Subject: [PATCH 43/93] Revert to returning nil instead of os.ErrNoDeadline --- p2p/transport/websocket/conn_browser.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 76a51f6c39..5eca1fbac6 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -7,7 +7,6 @@ import ( "errors" "io" "net" - "os" "strings" "sync" "syscall/js" @@ -137,15 +136,15 @@ func (c *Conn) RemoteAddr() net.Addr { } func (c *Conn) SetDeadline(t time.Time) error { - return os.ErrNoDeadline + return nil } func (c *Conn) SetReadDeadline(t time.Time) error { - return os.ErrNoDeadline + return nil } func (c *Conn) SetWriteDeadline(t time.Time) error { - return os.ErrNoDeadline + return nil } func (c *Conn) setUpHandlers() { From 7fcd4492cadf36e26efce9149030cb09678a7e2a Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 19 Aug 2019 13:08:18 -0700 Subject: [PATCH 44/93] Cache local and remote address --- p2p/transport/websocket/conn_browser.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 5eca1fbac6..2c34291bcf 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -35,6 +35,8 @@ type Conn struct { currData bytes.Buffer closeSignal chan struct{} dataSignal chan struct{} + localAddr net.Addr + remoteAddr net.Addr } // NewConn creates a Conn given a regular js/wasm WebSocket Conn. @@ -44,6 +46,8 @@ func NewConn(raw js.Value) *Conn { incomingData: make(chan []byte, incomingDataBufferSize), closeSignal: make(chan struct{}), dataSignal: make(chan struct{}), + localAddr: NewAddr("0.0.0.0:0"), + remoteAddr: getRemoteAddr(raw), } conn.setUpHandlers() go func() { @@ -125,16 +129,20 @@ func (c *Conn) Close() error { } func (c *Conn) LocalAddr() net.Addr { - return NewAddr("0.0.0.0:0") + return c.localAddr } -func (c *Conn) RemoteAddr() net.Addr { - rawURL := c.Get("url").String() +func getRemoteAddr(val js.Value) net.Addr { + rawURL := val.Get("url").String() withoutPrefix := strings.TrimPrefix(rawURL, "ws://") withoutSuffix := strings.TrimSuffix(withoutPrefix, "/") return NewAddr(withoutSuffix) } +func (c *Conn) RemoteAddr() net.Addr { + return c.remoteAddr +} + func (c *Conn) SetDeadline(t time.Time) error { return nil } From 9f099798d124aba4d426e2f3375c4722e3f7cfd2 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 19 Aug 2019 13:30:55 -0700 Subject: [PATCH 45/93] Optimize readBlob by using the readAsBinaryString method --- p2p/transport/websocket/conn_browser.go | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 2c34291bcf..25857ce91b 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -218,20 +218,12 @@ func readBlob(blob js.Value) []byte { reader := js.Global().Get("FileReader").New() dataChan := make(chan []byte) loadEndFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - // event.result is of type ArrayBuffer. We can convert it to a JavaScript - // Uint8Array, which is directly translatable to the Go type []byte. - buffer := reader.Get("result") - view := js.Global().Get("Uint8Array").New(buffer) - dataLen := view.Get("length").Int() - data := make([]byte, dataLen) - for i := 0; i < dataLen; i++ { - data[i] = byte(view.Index(i).Int()) - } + data := []byte(reader.Get("result").String()) dataChan <- data return nil }) defer loadEndFunc.Release() reader.Call("addEventListener", "loadend", loadEndFunc) - reader.Call("readAsArrayBuffer", blob) + reader.Call("readAsBinaryString", blob) return <-dataChan } From 49b017b696e418900c46effb3690980288b0b2a8 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 19 Aug 2019 13:59:36 -0700 Subject: [PATCH 46/93] Add better error handling to readBlob --- p2p/transport/websocket/conn_browser.go | 46 +++++++++++++++++++++---- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 25857ce91b..8f40053308 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -5,6 +5,7 @@ package websocket import ( "bytes" "errors" + "fmt" "io" "net" "strings" @@ -167,7 +168,11 @@ func (c *Conn) setUpHandlers() { // TODO(albrow): Currently we assume data is of type Blob. Really we // should check binaryType and then decode accordingly. blob := args[0].Get("data") - data := readBlob(blob) + data, err := readBlob(blob) + if err != nil { + // TODO(albrow): store and return error on next read. + panic(err) + } c.incomingData <- data }() return nil @@ -214,16 +219,43 @@ func (c *Conn) waitForOpen() error { // readBlob converts a JavaScript Blob into a slice of bytes. It uses the // FileReader API under the hood. -func readBlob(blob js.Value) []byte { +func readBlob(blob js.Value) ([]byte, error) { reader := js.Global().Get("FileReader").New() - dataChan := make(chan []byte) - loadEndFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + + // Set up two handlers, one for loadend and one for error. Each handler will + // send results through a channel. + dataChan := make(chan []byte, 1) + loadEndHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { data := []byte(reader.Get("result").String()) dataChan <- data return nil }) - defer loadEndFunc.Release() - reader.Call("addEventListener", "loadend", loadEndFunc) + defer loadEndHandler.Release() + reader.Call("addEventListener", "loadend", loadEndHandler) + errChan := make(chan error, 1) + errorHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + errChan <- convertJSError(args[0]) + return nil + }) + defer errorHandler.Release() + reader.Call("addEventListener", "error", errorHandler) + + // Call readAsBinaryString and wait to receive from either channel. reader.Call("readAsBinaryString", blob) - return <-dataChan + select { + case data := <-dataChan: + return data, nil + case err := <-errChan: + return nil, err + } +} + +func convertJSError(val js.Value) error { + var typ string + if gotType := val.Get("type"); gotType != js.Undefined() { + typ = gotType.String() + } else { + typ = val.Type().String() + } + return fmt.Errorf("JavaScript error: %s %s", typ, val.Get("message").String()) } From f85474b0afeaa348432c377b370252b16a0e84bf Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 19 Aug 2019 14:41:08 -0700 Subject: [PATCH 47/93] Revert back to using readAsArrayBuffer --- p2p/transport/websocket/conn_browser.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 8f40053308..17651117d2 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -226,7 +226,15 @@ func readBlob(blob js.Value) ([]byte, error) { // send results through a channel. dataChan := make(chan []byte, 1) loadEndHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - data := []byte(reader.Get("result").String()) + // event.result is of type ArrayBuffer. We can convert it to a JavaScript + // Uint8Array, which is directly translatable to the Go type []byte. + buffer := reader.Get("result") + view := js.Global().Get("Uint8Array").New(buffer) + dataLen := view.Get("length").Int() + data := make([]byte, dataLen) + for i := 0; i < dataLen; i++ { + data[i] = byte(view.Index(i).Int()) + } dataChan <- data return nil }) @@ -240,8 +248,8 @@ func readBlob(blob js.Value) ([]byte, error) { defer errorHandler.Release() reader.Call("addEventListener", "error", errorHandler) - // Call readAsBinaryString and wait to receive from either channel. - reader.Call("readAsBinaryString", blob) + // Call readAsArrayBuffer and wait to receive from either channel. + reader.Call("readAsArrayBuffer", blob) select { case data := <-dataChan: return data, nil From 42732897d527628b85b7185f9c3d4865682cd7e1 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 19 Aug 2019 15:30:06 -0700 Subject: [PATCH 48/93] Preserve message order by not using a goroutine in messageHandler --- p2p/transport/websocket/conn_browser.go | 29 +++++++++++-------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 17651117d2..be009e1070 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -31,7 +31,7 @@ type Conn struct { messageHandler *js.Func closeHandler *js.Func mut sync.Mutex - incomingData chan []byte + incomingData chan js.Value currDataMut sync.RWMutex currData bytes.Buffer closeSignal chan struct{} @@ -44,7 +44,7 @@ type Conn struct { func NewConn(raw js.Value) *Conn { conn := &Conn{ Value: raw, - incomingData: make(chan []byte, incomingDataBufferSize), + incomingData: make(chan js.Value, incomingDataBufferSize), closeSignal: make(chan struct{}), dataSignal: make(chan struct{}), localAddr: NewAddr("0.0.0.0:0"), @@ -164,17 +164,10 @@ func (c *Conn) setUpHandlers() { return } messageHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - go func() { - // TODO(albrow): Currently we assume data is of type Blob. Really we - // should check binaryType and then decode accordingly. - blob := args[0].Get("data") - data, err := readBlob(blob) - if err != nil { - // TODO(albrow): store and return error on next read. - panic(err) - } - c.incomingData <- data - }() + // TODO(albrow): Currently we assume data is of type Blob. Really we + // should check binaryType and then decode accordingly. + blob := args[0].Get("data") + c.incomingData <- blob return nil }) c.messageHandler = &messageHandler @@ -191,10 +184,13 @@ func (c *Conn) setUpHandlers() { // readLoop continuosly reads from the c.incoming data channel and writes to the // current data buffer. func (c *Conn) readLoop() error { - for data := range c.incomingData { - c.currDataMut.Lock() - _, err := c.currData.Write(data) + for blob := range c.incomingData { + data, err := readBlob(blob) if err != nil { + return err + } + c.currDataMut.Lock() + if _, err := c.currData.Write(data); err != nil { c.currDataMut.Unlock() return err } @@ -250,6 +246,7 @@ func readBlob(blob js.Value) ([]byte, error) { // Call readAsArrayBuffer and wait to receive from either channel. reader.Call("readAsArrayBuffer", blob) + select { case data := <-dataChan: return data, nil From 2eb6cb2d8de0336a9e86ce9bb57ae14d35001ec9 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 19 Aug 2019 17:11:41 -0700 Subject: [PATCH 49/93] Use ArrayBuffer type for binary data This allows us to avoid the rather obtuse FileReader API and makes it easier to read incoming data syncronously. --- p2p/transport/websocket/conn_browser.go | 110 +++++++----------------- 1 file changed, 32 insertions(+), 78 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index be009e1070..388769f32c 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -21,8 +21,6 @@ const ( webSocketStateClosed = 3 ) -const incomingDataBufferSize = 100 - var errConnectionClosed = errors.New("connection is closed") // Conn implements net.Conn interface for WebSockets in js/wasm. @@ -31,7 +29,6 @@ type Conn struct { messageHandler *js.Func closeHandler *js.Func mut sync.Mutex - incomingData chan js.Value currDataMut sync.RWMutex currData bytes.Buffer closeSignal chan struct{} @@ -43,21 +40,17 @@ type Conn struct { // NewConn creates a Conn given a regular js/wasm WebSocket Conn. func NewConn(raw js.Value) *Conn { conn := &Conn{ - Value: raw, - incomingData: make(chan js.Value, incomingDataBufferSize), - closeSignal: make(chan struct{}), - dataSignal: make(chan struct{}), - localAddr: NewAddr("0.0.0.0:0"), - remoteAddr: getRemoteAddr(raw), + Value: raw, + closeSignal: make(chan struct{}), + dataSignal: make(chan struct{}), + localAddr: NewAddr("0.0.0.0:0"), + remoteAddr: getRemoteAddr(raw), } + // Force the JavaScript WebSockets API to use the ArrayBuffer type for + // incoming messages instead of the Blob type. This is better for us because + // ArrayBuffer can be converted to []byte synchronously but Blob cannot. + conn.Set("binaryType", "arraybuffer") conn.setUpHandlers() - go func() { - // TODO(albrow): Handle error appropriately - err := conn.readLoop() - if err != nil { - panic(err) - } - }() return conn } @@ -164,10 +157,21 @@ func (c *Conn) setUpHandlers() { return } messageHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - // TODO(albrow): Currently we assume data is of type Blob. Really we - // should check binaryType and then decode accordingly. - blob := args[0].Get("data") - c.incomingData <- blob + arrayBuffer := args[0].Get("data") + data := arrayBufferToBytes(arrayBuffer) + c.currDataMut.Lock() + if _, err := c.currData.Write(data); err != nil { + c.currDataMut.Unlock() + return err + } + c.currDataMut.Unlock() + + // Non-blocking signal + select { + case c.dataSignal <- struct{}{}: + default: + } + return nil }) c.messageHandler = &messageHandler @@ -181,25 +185,6 @@ func (c *Conn) setUpHandlers() { c.Call("addEventListener", "close", closeHandler) } -// readLoop continuosly reads from the c.incoming data channel and writes to the -// current data buffer. -func (c *Conn) readLoop() error { - for blob := range c.incomingData { - data, err := readBlob(blob) - if err != nil { - return err - } - c.currDataMut.Lock() - if _, err := c.currData.Write(data); err != nil { - c.currDataMut.Unlock() - return err - } - c.currDataMut.Unlock() - c.dataSignal <- struct{}{} - } - return nil -} - func (c *Conn) waitForOpen() error { openSignal := make(chan struct{}) handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { @@ -213,46 +198,15 @@ func (c *Conn) waitForOpen() error { return nil } -// readBlob converts a JavaScript Blob into a slice of bytes. It uses the -// FileReader API under the hood. -func readBlob(blob js.Value) ([]byte, error) { - reader := js.Global().Get("FileReader").New() - - // Set up two handlers, one for loadend and one for error. Each handler will - // send results through a channel. - dataChan := make(chan []byte, 1) - loadEndHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - // event.result is of type ArrayBuffer. We can convert it to a JavaScript - // Uint8Array, which is directly translatable to the Go type []byte. - buffer := reader.Get("result") - view := js.Global().Get("Uint8Array").New(buffer) - dataLen := view.Get("length").Int() - data := make([]byte, dataLen) - for i := 0; i < dataLen; i++ { - data[i] = byte(view.Index(i).Int()) - } - dataChan <- data - return nil - }) - defer loadEndHandler.Release() - reader.Call("addEventListener", "loadend", loadEndHandler) - errChan := make(chan error, 1) - errorHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - errChan <- convertJSError(args[0]) - return nil - }) - defer errorHandler.Release() - reader.Call("addEventListener", "error", errorHandler) - - // Call readAsArrayBuffer and wait to receive from either channel. - reader.Call("readAsArrayBuffer", blob) - - select { - case data := <-dataChan: - return data, nil - case err := <-errChan: - return nil, err +// arrayBufferToBytes converts a JavaScript ArrayBuffer to a slice of bytes. +func arrayBufferToBytes(buffer js.Value) []byte { + view := js.Global().Get("Uint8Array").New(buffer) + dataLen := view.Get("length").Int() + data := make([]byte, dataLen) + for i := 0; i < dataLen; i++ { + data[i] = byte(view.Index(i).Int()) } + return data } func convertJSError(val js.Value) error { From e327fad1620c9aa6169a74cd1b34035d62013853 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 19 Aug 2019 17:15:57 -0700 Subject: [PATCH 50/93] Use a buffer of 1 for dataSignal channel --- p2p/transport/websocket/conn_browser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 388769f32c..7da0027bdb 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -42,7 +42,7 @@ func NewConn(raw js.Value) *Conn { conn := &Conn{ Value: raw, closeSignal: make(chan struct{}), - dataSignal: make(chan struct{}), + dataSignal: make(chan struct{}, 1), localAddr: NewAddr("0.0.0.0:0"), remoteAddr: getRemoteAddr(raw), } From eda7468e8b924736289346969c2882e04639739d Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 19 Aug 2019 17:38:26 -0700 Subject: [PATCH 51/93] Add TODO for returning os.ErrNoDeadline --- p2p/transport/websocket/conn_browser.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 7da0027bdb..ffd38c5932 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -137,6 +137,8 @@ func (c *Conn) RemoteAddr() net.Addr { return c.remoteAddr } +// TODO: Return os.ErrNoDeadline. For now we return nil because multiplexers +// don't handle the error correctly. func (c *Conn) SetDeadline(t time.Time) error { return nil } From 38b0b7fbbed72fd362535fdd775e17bbf1607673 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 19 Aug 2019 18:41:58 -0700 Subject: [PATCH 52/93] Add proper error handling --- p2p/transport/websocket/conn_browser.go | 88 ++++++++++++++++++-- p2p/transport/websocket/websocket_browser.go | 8 +- 2 files changed, 82 insertions(+), 14 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index ffd38c5932..07b88daca1 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -28,13 +28,16 @@ type Conn struct { js.Value messageHandler *js.Func closeHandler *js.Func + errorHandler *js.Func mut sync.Mutex currDataMut sync.RWMutex currData bytes.Buffer + closeOnce sync.Once closeSignal chan struct{} dataSignal chan struct{} localAddr net.Addr remoteAddr net.Addr + firstErr error } // NewConn creates a Conn given a regular js/wasm WebSocket Conn. @@ -56,7 +59,7 @@ func NewConn(raw js.Value) *Conn { func (c *Conn) Read(b []byte) (int, error) { if err := c.checkOpen(); err != nil { - return 0, io.EOF + return c.readAfterErr(b) } for { @@ -73,7 +76,7 @@ func (c *Conn) Read(b []byte) (int, error) { case <-c.dataSignal: continue case <-c.closeSignal: - return 0, io.EOF + return c.readAfterErr(b) } } else { return n, err @@ -81,6 +84,22 @@ func (c *Conn) Read(b []byte) (int, error) { } } +// readAfterError reads from c.currData. If there is no more data left it +// returns c.firstErr if non-nil and otherwise returns io.EOF. +func (c *Conn) readAfterErr(b []byte) (int, error) { + c.currDataMut.RLock() + n, err := c.currData.Read(b) + c.currDataMut.RUnlock() + if n == 0 { + if c.firstErr != nil { + return 0, c.firstErr + } else { + return 0, io.EOF + } + } + return n, err +} + // checkOpen returns an error if the connection is not open. Otherwise, it // returns nil. func (c *Conn) checkOpen() error { @@ -108,9 +127,21 @@ func (c *Conn) Write(b []byte) (n int, err error) { // close error, subsequent and concurrent calls will return nil. // This method is thread-safe. func (c *Conn) Close() error { + c.signalClose() + c.Call("close") + c.releaseHandlers() + return nil +} + +func (c *Conn) signalClose() { + c.closeOnce.Do(func() { + close(c.closeSignal) + }) +} + +func (c *Conn) releaseHandlers() { c.mut.Lock() defer c.mut.Unlock() - c.Call("close") if c.messageHandler != nil { c.Call("removeEventListener", "message", *c.messageHandler) c.messageHandler.Release() @@ -119,7 +150,10 @@ func (c *Conn) Close() error { c.Call("removeEventListener", "close", *c.closeHandler) c.closeHandler.Release() } - return nil + if c.errorHandler != nil { + c.Call("removeEventListener", "error", *c.errorHandler) + c.errorHandler.Release() + } } func (c *Conn) LocalAddr() net.Addr { @@ -180,11 +214,27 @@ func (c *Conn) setUpHandlers() { c.Call("addEventListener", "message", messageHandler) closeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - close(c.closeSignal) + go func() { + c.signalClose() + c.mut.Lock() + // Store the error in c.firstErr. It will be returned by Read later on. + c.firstErr = errorEventToError(args[0]) + c.mut.Unlock() + c.releaseHandlers() + }() return nil }) c.closeHandler = &closeHandler c.Call("addEventListener", "close", closeHandler) + + errorHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + // Unfortunately, the "error" event doesn't appear to give us any useful + // information. All we can do is close the connection. + c.Close() + return nil + }) + c.errorHandler = &errorHandler + c.Call("addEventListener", "error", errorHandler) } func (c *Conn) waitForOpen() error { @@ -196,8 +246,14 @@ func (c *Conn) waitForOpen() error { defer c.Call("removeEventListener", "open", handler) defer handler.Release() c.Call("addEventListener", "open", handler) - <-openSignal - return nil + select { + case <-openSignal: + return nil + case <-c.closeSignal: + // c.closeSignal means there was an error when trying to open the + // connection. + return c.firstErr + } } // arrayBufferToBytes converts a JavaScript ArrayBuffer to a slice of bytes. @@ -211,12 +267,26 @@ func arrayBufferToBytes(buffer js.Value) []byte { return data } -func convertJSError(val js.Value) error { +func errorEventToError(val js.Value) error { var typ string if gotType := val.Get("type"); gotType != js.Undefined() { typ = gotType.String() } else { typ = val.Type().String() } - return fmt.Errorf("JavaScript error: %s %s", typ, val.Get("message").String()) + var reason string + if gotReason := val.Get("reason"); gotReason != js.Undefined() && gotReason.String() != "" { + reason = gotReason.String() + } else { + code := val.Get("code") + if code != js.Undefined() { + switch code := code.Int(); code { + case 1006: + reason = "code 1006: connection unexpectedly closed" + default: + reason = fmt.Sprintf("unexpected code: %d", code) + } + } + } + return fmt.Errorf("JavaScript error: (%s) %s", typ, reason) } diff --git a/p2p/transport/websocket/websocket_browser.go b/p2p/transport/websocket/websocket_browser.go index 9002533e0c..acf306f5cd 100644 --- a/p2p/transport/websocket/websocket_browser.go +++ b/p2p/transport/websocket/websocket_browser.go @@ -19,12 +19,10 @@ func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (ma } rawConn := js.Global().Get("WebSocket").New(wsurl) - rawConn.Call("addEventListener", "error", js.FuncOf(func(this js.Value, args []js.Value) interface{} { - js.Global().Get("console").Call("log", args[0]) - return nil - })) conn := NewConn(rawConn) - conn.waitForOpen() + if err := conn.waitForOpen(); err != nil { + return nil, err + } mnc, err := manet.WrapNetConn(conn) if err != nil { conn.Close() From b8d9682b7bc797774d565aa920b0df175f81cbbd Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Tue, 17 Sep 2019 12:39:29 -0700 Subject: [PATCH 53/93] Expand comment about time.Sleep hack --- p2p/transport/websocket/browser_integration_browser_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/p2p/transport/websocket/browser_integration_browser_test.go b/p2p/transport/websocket/browser_integration_browser_test.go index 656db447ba..6a55c30275 100644 --- a/p2p/transport/websocket/browser_integration_browser_test.go +++ b/p2p/transport/websocket/browser_integration_browser_test.go @@ -51,6 +51,9 @@ func TestInBrowser(t *testing.T) { } // TODO(albrow): This hack is necessary in order to give the reader time to - // finish. We should find some way to remove it. + // finish. As soon as this test function returns, the browser window is + // closed, which means there is no time for the other end of the connection to + // read the "pong" message. We should find some way to remove this hack if + // possible. time.Sleep(1 * time.Second) } From e08426cc8d11465fbfdd4f71b97baf50e2a02ee4 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Tue, 17 Sep 2019 12:51:09 -0700 Subject: [PATCH 54/93] Call conn.Close if waitForOpen returns an error --- p2p/transport/websocket/websocket_browser.go | 1 + 1 file changed, 1 insertion(+) diff --git a/p2p/transport/websocket/websocket_browser.go b/p2p/transport/websocket/websocket_browser.go index acf306f5cd..258a82917c 100644 --- a/p2p/transport/websocket/websocket_browser.go +++ b/p2p/transport/websocket/websocket_browser.go @@ -21,6 +21,7 @@ func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (ma rawConn := js.Global().Get("WebSocket").New(wsurl) conn := NewConn(rawConn) if err := conn.waitForOpen(); err != nil { + conn.Close() return nil, err } mnc, err := manet.WrapNetConn(conn) From fe9ccdf21a8e6af72fd5e2eb1ff36e6d8003ee32 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Tue, 17 Sep 2019 12:57:46 -0700 Subject: [PATCH 55/93] Simplify Conn.Read logic --- p2p/transport/websocket/conn_browser.go | 29 ++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 07b88daca1..73ee2aee4d 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -64,22 +64,21 @@ func (c *Conn) Read(b []byte) (int, error) { for { c.currDataMut.RLock() - n, err := c.currData.Read(b) + n, _ := c.currData.Read(b) c.currDataMut.RUnlock() - if err != nil && err != io.EOF { - // Return any unexpected errors immediately. - return n, err - } else if n == 0 || err == io.EOF { - // There is no data ready to be read. Wait for more data or for the - // connection to be closed. - select { - case <-c.dataSignal: - continue - case <-c.closeSignal: - return c.readAfterErr(b) - } - } else { - return n, err + + if n != 0 { + // Data was ready. Return the number of bytes read. + return n, nil + } + + // There is no data ready to be read. Wait for more data or for the + // connection to be closed. + select { + case <-c.dataSignal: + continue + case <-c.closeSignal: + return c.readAfterErr(b) } } } From 169c516ab7deb78dcc4a98d610adf3fd1f58c796 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Tue, 17 Sep 2019 13:12:55 -0700 Subject: [PATCH 56/93] Simplify iteration over Uint8Array --- p2p/transport/websocket/conn_browser.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 73ee2aee4d..3e4d28aa58 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -115,8 +115,8 @@ func (c *Conn) Write(b []byte) (n int, err error) { return 0, err } uint8Array := js.Global().Get("Uint8Array").New(len(b)) - for i := 0; i < len(b); i++ { - uint8Array.SetIndex(i, b[i]) + for i, bi := range b { + uint8Array.SetIndex(i, bi) } c.Call("send", uint8Array.Get("buffer")) return len(b), nil From b4e7083efe474cd32cdf1ad02136245930f50d71 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Tue, 17 Sep 2019 13:33:28 -0700 Subject: [PATCH 57/93] Set handlers to nil after releasing them --- p2p/transport/websocket/conn_browser.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 3e4d28aa58..733ca2b828 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -144,14 +144,17 @@ func (c *Conn) releaseHandlers() { if c.messageHandler != nil { c.Call("removeEventListener", "message", *c.messageHandler) c.messageHandler.Release() + c.messageHandler = nil } if c.closeHandler != nil { c.Call("removeEventListener", "close", *c.closeHandler) c.closeHandler.Release() + c.closeHandler = nil } if c.errorHandler != nil { c.Call("removeEventListener", "error", *c.errorHandler) c.errorHandler.Release() + c.errorHandler = nil } } From a4d3db5f8575d3e599fc1d7e73bcdcda5cc77baa Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Tue, 17 Sep 2019 14:45:34 -0700 Subject: [PATCH 58/93] Use a sync.Once in Conn.Close --- p2p/transport/websocket/conn_browser.go | 35 ++++++++++++++----------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 733ca2b828..f676b303bc 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -26,18 +26,19 @@ var errConnectionClosed = errors.New("connection is closed") // Conn implements net.Conn interface for WebSockets in js/wasm. type Conn struct { js.Value - messageHandler *js.Func - closeHandler *js.Func - errorHandler *js.Func - mut sync.Mutex - currDataMut sync.RWMutex - currData bytes.Buffer - closeOnce sync.Once - closeSignal chan struct{} - dataSignal chan struct{} - localAddr net.Addr - remoteAddr net.Addr - firstErr error + messageHandler *js.Func + closeHandler *js.Func + errorHandler *js.Func + mut sync.Mutex + currDataMut sync.RWMutex + currData bytes.Buffer + closeOnce sync.Once + closeSignalOnce sync.Once + closeSignal chan struct{} + dataSignal chan struct{} + localAddr net.Addr + remoteAddr net.Addr + firstErr error } // NewConn creates a Conn given a regular js/wasm WebSocket Conn. @@ -126,14 +127,16 @@ func (c *Conn) Write(b []byte) (n int, err error) { // close error, subsequent and concurrent calls will return nil. // This method is thread-safe. func (c *Conn) Close() error { - c.signalClose() - c.Call("close") - c.releaseHandlers() + c.closeOnce.Do(func() { + c.signalClose() + c.Call("close") + c.releaseHandlers() + }) return nil } func (c *Conn) signalClose() { - c.closeOnce.Do(func() { + c.closeSignalOnce.Do(func() { close(c.closeSignal) }) } From 9ec49775da86263190782a4bad060ad4e8b1b870 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Tue, 17 Sep 2019 15:20:54 -0700 Subject: [PATCH 59/93] Recover from uncaught JavaScript exceptions in Conn.Write --- p2p/transport/websocket/conn_browser.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index f676b303bc..203fcdabd8 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -112,6 +112,11 @@ func (c *Conn) checkOpen() error { } func (c *Conn) Write(b []byte) (n int, err error) { + defer func() { + if e := recover(); e != nil { + err = recoveredValueToError(e) + } + }() if err := c.checkOpen(); err != nil { return 0, err } @@ -295,3 +300,12 @@ func errorEventToError(val js.Value) error { } return fmt.Errorf("JavaScript error: (%s) %s", typ, reason) } + +func recoveredValueToError(e interface{}) error { + switch e := e.(type) { + case error: + return e + default: + return fmt.Errorf("recovered from unexpected panic: %T %s", e, e) + } +} From badfe1f1308af05d210f9e2186f98f793ac795ce Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Tue, 17 Sep 2019 15:44:36 -0700 Subject: [PATCH 60/93] Switch order of Call("close") and signalClose() --- p2p/transport/websocket/conn_browser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 203fcdabd8..b603ebbe48 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -133,8 +133,8 @@ func (c *Conn) Write(b []byte) (n int, err error) { // This method is thread-safe. func (c *Conn) Close() error { c.closeOnce.Do(func() { - c.signalClose() c.Call("close") + c.signalClose() c.releaseHandlers() }) return nil From 391b41eef53ef12e3e526120fe3964bedbbbf691 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Tue, 17 Sep 2019 16:00:36 -0700 Subject: [PATCH 61/93] Remove unused constant --- p2p/transport/websocket/browser_integration_native_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/p2p/transport/websocket/browser_integration_native_test.go b/p2p/transport/websocket/browser_integration_native_test.go index 464fca73a8..f960822fc6 100644 --- a/p2p/transport/websocket/browser_integration_native_test.go +++ b/p2p/transport/websocket/browser_integration_native_test.go @@ -17,10 +17,6 @@ import ( ma "github.com/multiformats/go-multiaddr" ) -const ( - testServerPort = ":9714" -) - // TestInBrowser is a harness that allows us to use `go test` in order to run // WebAssembly tests in a headless browser. func TestInBrowser(t *testing.T) { From 9da7d27a46b353e7eeebada29a102ca0baafed1f Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 30 Sep 2019 15:09:36 -0700 Subject: [PATCH 62/93] Set firstErr before signaling the connection close --- p2p/transport/websocket/conn_browser.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index b603ebbe48..73d087ccfc 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -38,7 +38,7 @@ type Conn struct { dataSignal chan struct{} localAddr net.Addr remoteAddr net.Addr - firstErr error + firstErr error // only read this _after_ observing that closeSignal has been closed. } // NewConn creates a Conn given a regular js/wasm WebSocket Conn. @@ -134,14 +134,15 @@ func (c *Conn) Write(b []byte) (n int, err error) { func (c *Conn) Close() error { c.closeOnce.Do(func() { c.Call("close") - c.signalClose() + c.signalClose(nil) c.releaseHandlers() }) return nil } -func (c *Conn) signalClose() { +func (c *Conn) signalClose(err error) { c.closeSignalOnce.Do(func() { + c.firstErr = err close(c.closeSignal) }) } @@ -225,11 +226,7 @@ func (c *Conn) setUpHandlers() { closeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { go func() { - c.signalClose() - c.mut.Lock() - // Store the error in c.firstErr. It will be returned by Read later on. - c.firstErr = errorEventToError(args[0]) - c.mut.Unlock() + c.signalClose(errorEventToError(args[0])) c.releaseHandlers() }() return nil From 908e0da96a83f716dbe7f2d098844f601e9762f0 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 30 Sep 2019 15:18:58 -0700 Subject: [PATCH 63/93] Handle connection errors before handling data. --- p2p/transport/websocket/conn_browser.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 73d087ccfc..1f021529d1 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -6,7 +6,6 @@ import ( "bytes" "errors" "fmt" - "io" "net" "strings" "sync" @@ -59,8 +58,10 @@ func NewConn(raw js.Value) *Conn { } func (c *Conn) Read(b []byte) (int, error) { - if err := c.checkOpen(); err != nil { - return c.readAfterErr(b) + select { + case <-c.closeSignal: + c.readAfterErr(b) + default: } for { @@ -77,7 +78,6 @@ func (c *Conn) Read(b []byte) (int, error) { // connection to be closed. select { case <-c.dataSignal: - continue case <-c.closeSignal: return c.readAfterErr(b) } @@ -87,16 +87,12 @@ func (c *Conn) Read(b []byte) (int, error) { // readAfterError reads from c.currData. If there is no more data left it // returns c.firstErr if non-nil and otherwise returns io.EOF. func (c *Conn) readAfterErr(b []byte) (int, error) { + if c.firstErr != nil { + return 0, c.firstErr + } c.currDataMut.RLock() n, err := c.currData.Read(b) c.currDataMut.RUnlock() - if n == 0 { - if c.firstErr != nil { - return 0, c.firstErr - } else { - return 0, io.EOF - } - } return n, err } From 88b65ce76553b6846eeb1de93b40129d1826257a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 30 Sep 2019 15:56:32 -0700 Subject: [PATCH 64/93] test: auto-install wasm test dependency --- .../browser_integration_native_test.go | 39 +++++++++++++++++-- p2p/transport/websocket/tools/go.mod | 5 +++ p2p/transport/websocket/tools/go.sum | 31 +++++++++++++++ p2p/transport/websocket/tools/tools.go | 5 +++ 4 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 p2p/transport/websocket/tools/go.mod create mode 100644 p2p/transport/websocket/tools/go.sum create mode 100644 p2p/transport/websocket/tools/tools.go diff --git a/p2p/transport/websocket/browser_integration_native_test.go b/p2p/transport/websocket/browser_integration_native_test.go index f960822fc6..fa940b5c53 100644 --- a/p2p/transport/websocket/browser_integration_native_test.go +++ b/p2p/transport/websocket/browser_integration_native_test.go @@ -4,7 +4,6 @@ package websocket import ( "bufio" - "fmt" "os" "os/exec" "path/filepath" @@ -17,9 +16,35 @@ import ( ma "github.com/multiformats/go-multiaddr" ) +var ( + wasmBrowserTestBin = "wasmbrowsertest" + wasmBrowserTestDir = filepath.Join("tools", "bin") + wasmBrowserTestPackage = "github.com/agnivade/wasmbrowsertest" +) + // TestInBrowser is a harness that allows us to use `go test` in order to run // WebAssembly tests in a headless browser. func TestInBrowser(t *testing.T) { + // ensure we have the right tools. + err := os.MkdirAll(wasmBrowserTestDir, 0755) + + t.Logf("building %s", wasmBrowserTestPackage) + if err != nil && !os.IsExist(err) { + t.Fatal(err) + } + + cmd := exec.Command( + "go", "build", + "-o", wasmBrowserTestBin, + "github.com/agnivade/wasmbrowsertest", + ) + cmd.Dir = wasmBrowserTestDir + err = cmd.Run() + if err != nil { + t.Fatal(err) + } + t.Log("starting server") + // Start a transport which the browser peer will dial. serverDoneSignal := make(chan struct{}) go func() { @@ -62,13 +87,19 @@ func TestInBrowser(t *testing.T) { } }() - testExecPath := filepath.Join(os.Getenv("GOPATH"), "bin", "wasmbrowsertest") - cmd := exec.Command("go", "test", "-exec", testExecPath, "-run", "TestInBrowser", ".", "-v") + t.Log("starting browser") + + cmd = exec.Command( + "go", "test", "-v", + "-exec", filepath.Join(wasmBrowserTestDir, wasmBrowserTestBin), + "-run", "TestInBrowser", + ".", + ) cmd.Env = append(os.Environ(), []string{"GOOS=js", "GOARCH=wasm"}...) output, err := cmd.CombinedOutput() if err != nil { formattedOutput := "\t" + strings.Join(strings.Split(string(output), "\n"), "\n\t") - fmt.Println("BROWSER OUTPUT:\n", formattedOutput) + t.Log("BROWSER OUTPUT:\n", formattedOutput) t.Fatal("BROWSER:", err) } diff --git a/p2p/transport/websocket/tools/go.mod b/p2p/transport/websocket/tools/go.mod new file mode 100644 index 0000000000..d34d7385b2 --- /dev/null +++ b/p2p/transport/websocket/tools/go.mod @@ -0,0 +1,5 @@ +module github.com/libp2p/go-ws-transport/tools + +go 1.13 + +require github.com/agnivade/wasmbrowsertest v0.3.1 diff --git a/p2p/transport/websocket/tools/go.sum b/p2p/transport/websocket/tools/go.sum new file mode 100644 index 0000000000..94b9c35ab1 --- /dev/null +++ b/p2p/transport/websocket/tools/go.sum @@ -0,0 +1,31 @@ +github.com/agnivade/wasmbrowsertest v0.3.1 h1:bA9aA+bcp7KuqGvmCuBdnMqy6PXxFjYP7FxsaT+JSqc= +github.com/agnivade/wasmbrowsertest v0.3.1/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI= +github.com/chromedp/cdproto v0.0.0-20190614062957-d6d2f92b486d/go.mod h1:S8mB5wY3vV+vRIzf39xDXsw3XKYewW9X6rW2aEmkrSw= +github.com/chromedp/cdproto v0.0.0-20190621002710-8cbd498dd7a0 h1:4Wocv9f+KWF4GtZudyrn8JSBTgHQbGp86mcsoH7j1iQ= +github.com/chromedp/cdproto v0.0.0-20190621002710-8cbd498dd7a0/go.mod h1:S8mB5wY3vV+vRIzf39xDXsw3XKYewW9X6rW2aEmkrSw= +github.com/chromedp/chromedp v0.3.1-0.20190619195644-fd957a4d2901 h1:tg66ykM8VYqP9k4DFQwSMnYv84HNTruF+GR6kefFNg4= +github.com/chromedp/chromedp v0.3.1-0.20190619195644-fd957a4d2901/go.mod h1:mJdvfrVn594N9tfiPecUidF6W5jPRKHymqHfzbobPsM= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/go-interpreter/wagon v0.5.1-0.20190713202023-55a163980b6c h1:DLLAPVFrk9iNzljMKF512CUmrFImQ6WU3sDiUS4IRqk= +github.com/go-interpreter/wagon v0.5.1-0.20190713202023-55a163980b6c/go.mod h1:5+b/MBYkclRZngKF5s6qrgWxSLgE9F5dFdO1hAueZLc= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f h1:Jnx61latede7zDD3DiiP4gmNz33uK0U5HDUaF0a/HVQ= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/knq/sysutil v0.0.0-20181215143952-f05b59f0f307 h1:vl4eIlySbjertFaNwiMjXsGrFVK25aOWLq7n+3gh2ls= +github.com/knq/sysutil v0.0.0-20181215143952-f05b59f0f307/go.mod h1:BjPj+aVjl9FW/cCGiF3nGh5v+9Gd3VCgBQbod/GlMaQ= +github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481 h1:IaSjLMT6WvkoZZjspGxy3rdaTEmWLoRm49WbtVUi9sA= +github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc h1:RTUQlKzoZZVG3umWNzOYeFecQLIh+dbxXvJp1zPQJTI= +github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc/go.mod h1:NoCfSFWosfqMqmmD7hApkirIK9ozpHjxRnRxs1l413A= +golang.org/x/sys v0.0.0-20190306220234-b354f8bf4d9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190618155005-516e3c20635f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/p2p/transport/websocket/tools/tools.go b/p2p/transport/websocket/tools/tools.go new file mode 100644 index 0000000000..d051c510b9 --- /dev/null +++ b/p2p/transport/websocket/tools/tools.go @@ -0,0 +1,5 @@ +// +build tools + +package tools + +import _ "github.com/agnivade/wasmbrowsertest" From 3d337a9a107c347913444b6acdcd587b79445b2a Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 17 Dec 2019 13:49:35 +0200 Subject: [PATCH 65/93] add mutex for write/close --- p2p/transport/websocket/conn_native.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/p2p/transport/websocket/conn_native.go b/p2p/transport/websocket/conn_native.go index 6be4697ec6..bd84b7dce9 100644 --- a/p2p/transport/websocket/conn_native.go +++ b/p2p/transport/websocket/conn_native.go @@ -17,6 +17,7 @@ type Conn struct { DefaultMessageType int reader io.Reader closeOnce sync.Once + mx sync.Mutex } func (c *Conn) Read(b []byte) (int, error) { @@ -67,6 +68,9 @@ func (c *Conn) prepNextReader() error { } func (c *Conn) Write(b []byte) (n int, err error) { + c.mx.Lock() + defer c.mx.Unlock() + if err := c.Conn.WriteMessage(c.DefaultMessageType, b); err != nil { return 0, err } @@ -78,6 +82,9 @@ func (c *Conn) Write(b []byte) (n int, err error) { // close error, subsequent and concurrent calls will return nil. // This method is thread-safe. func (c *Conn) Close() error { + c.mx.Lock() + defer c.mx.Unlock() + var err error c.closeOnce.Do(func() { err1 := c.Conn.WriteControl( From 4102c54de012bcd6cbbab687d11ada22b0838db4 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 9 Jan 2020 09:36:15 +0100 Subject: [PATCH 66/93] feat: faster copy in wasm Use the new CopyBytesTo* functions to avoid copying byte by byte. --- p2p/transport/websocket/conn_browser.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 1f021529d1..83f07a2f52 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -117,8 +117,8 @@ func (c *Conn) Write(b []byte) (n int, err error) { return 0, err } uint8Array := js.Global().Get("Uint8Array").New(len(b)) - for i, bi := range b { - uint8Array.SetIndex(i, bi) + if js.CopyBytesToJS(uint8Array, b) != len(b) { + panic("expected to copy all bytes") } c.Call("send", uint8Array.Get("buffer")) return len(b), nil @@ -262,10 +262,10 @@ func (c *Conn) waitForOpen() error { // arrayBufferToBytes converts a JavaScript ArrayBuffer to a slice of bytes. func arrayBufferToBytes(buffer js.Value) []byte { view := js.Global().Get("Uint8Array").New(buffer) - dataLen := view.Get("length").Int() + dataLen := view.Length() data := make([]byte, dataLen) - for i := 0; i < dataLen; i++ { - data[i] = byte(view.Index(i).Int()) + if js.CopyBytesToGo(data, view) != dataLen { + panic("expected to copy all bytes") } return data } From 6463b1cf6194716c459845467e3bb91540eadc9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 22 Jan 2020 17:51:44 +0000 Subject: [PATCH 67/93] add licenses. (#69) --- p2p/transport/websocket/LICENSE-APACHE | 5 +++++ p2p/transport/websocket/LICENSE-MIT | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 p2p/transport/websocket/LICENSE-APACHE create mode 100644 p2p/transport/websocket/LICENSE-MIT diff --git a/p2p/transport/websocket/LICENSE-APACHE b/p2p/transport/websocket/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/p2p/transport/websocket/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/p2p/transport/websocket/LICENSE-MIT b/p2p/transport/websocket/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/p2p/transport/websocket/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. From df4b7d636eb9b24042bb71b9143ec00de7d12b9a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 4 Feb 2020 10:08:27 -0800 Subject: [PATCH 68/93] Revert "add mutex for write/close" Actually fixed in https://github.com/multiformats/go-multistream/pull/50 This reverts commit 3d337a9a107c347913444b6acdcd587b79445b2a. --- p2p/transport/websocket/conn_native.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/p2p/transport/websocket/conn_native.go b/p2p/transport/websocket/conn_native.go index bd84b7dce9..6be4697ec6 100644 --- a/p2p/transport/websocket/conn_native.go +++ b/p2p/transport/websocket/conn_native.go @@ -17,7 +17,6 @@ type Conn struct { DefaultMessageType int reader io.Reader closeOnce sync.Once - mx sync.Mutex } func (c *Conn) Read(b []byte) (int, error) { @@ -68,9 +67,6 @@ func (c *Conn) prepNextReader() error { } func (c *Conn) Write(b []byte) (n int, err error) { - c.mx.Lock() - defer c.mx.Unlock() - if err := c.Conn.WriteMessage(c.DefaultMessageType, b); err != nil { return 0, err } @@ -82,9 +78,6 @@ func (c *Conn) Write(b []byte) (n int, err error) { // close error, subsequent and concurrent calls will return nil. // This method is thread-safe. func (c *Conn) Close() error { - c.mx.Lock() - defer c.mx.Unlock() - var err error c.closeOnce.Do(func() { err1 := c.Conn.WriteControl( From d0bf192109b7ec35b2207a783e01ba78ba85c151 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 2 Apr 2020 22:12:24 -0700 Subject: [PATCH 69/93] fix: restrict dials to IP + TCP That is, forbid DNS. See https://github.com/libp2p/go-libp2p/issues/841 --- p2p/transport/websocket/websocket.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 7e1c900f27..ddd4141090 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -18,7 +18,7 @@ import ( var WsProtocol = ma.ProtocolWithCode(ma.P_WS) // WsFmt is multiaddr formatter for WsProtocol -var WsFmt = mafmt.And(mafmt.TCP, mafmt.Base(WsProtocol.Code)) +var WsFmt = mafmt.And(mafmt.TCP, mafmt.Base(ma.P_WS)) // WsCodec is the multiaddr-net codec definition for the websocket transport var WsCodec = &manet.NetCodec{ @@ -28,6 +28,10 @@ var WsCodec = &manet.NetCodec{ ParseNetAddr: ParseWebsocketNetAddr, } +// This is _not_ WsFmt because we want the transport to stick to dialing fully +// resolved addresses. +var dialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_TCP), mafmt.Base(ma.P_WS)) + func init() { manet.RegisterNetCodec(WsCodec) } @@ -44,7 +48,7 @@ func New(u *tptu.Upgrader) *WebsocketTransport { } func (t *WebsocketTransport) CanDial(a ma.Multiaddr) bool { - return WsFmt.Matches(a) + return dialMatcher.Matches(a) } func (t *WebsocketTransport) Protocols() []int { From ee27334a62c9166ad2b673f120f7de22715fec36 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 21 Apr 2020 23:17:45 -0700 Subject: [PATCH 70/93] fix: add read/write locks We keep running into races so I figured we might as well just make this all thread-safe. It should have no effect on performance. The actual _bug_ was setting the write deadline while writing. But we might as well just have read and write locks. fixes https://github.com/libp2p/go-libp2p-swarm/issues/205 --- p2p/transport/websocket/conn_native.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/p2p/transport/websocket/conn_native.go b/p2p/transport/websocket/conn_native.go index 6be4697ec6..7a0daeacaf 100644 --- a/p2p/transport/websocket/conn_native.go +++ b/p2p/transport/websocket/conn_native.go @@ -17,9 +17,14 @@ type Conn struct { DefaultMessageType int reader io.Reader closeOnce sync.Once + + readLock, writeLock sync.Mutex } func (c *Conn) Read(b []byte) (int, error) { + c.readLock.Lock() + defer c.readLock.Unlock() + if c.reader == nil { if err := c.prepNextReader(); err != nil { return 0, err @@ -67,6 +72,9 @@ func (c *Conn) prepNextReader() error { } func (c *Conn) Write(b []byte) (n int, err error) { + c.writeLock.Lock() + defer c.writeLock.Unlock() + if err := c.Conn.WriteMessage(c.DefaultMessageType, b); err != nil { return 0, err } @@ -113,10 +121,18 @@ func (c *Conn) SetDeadline(t time.Time) error { } func (c *Conn) SetReadDeadline(t time.Time) error { + // Don't lock when setting the read deadline. That would prevent us from + // interrupting an in-progress read. return c.Conn.SetReadDeadline(t) } func (c *Conn) SetWriteDeadline(t time.Time) error { + // Unlike the read deadline, we need to lock when setting the write + // deadline. + + c.writeLock.Lock() + defer c.writeLock.Unlock() + return c.Conn.SetWriteDeadline(t) } From 5a6d40c1ee101256af5b6e1dd06bbdd4d11da650 Mon Sep 17 00:00:00 2001 From: "M. Hawn" Date: Thu, 15 Oct 2020 12:02:03 -0600 Subject: [PATCH 71/93] Update for go 1.14 Wasm changes --- p2p/transport/websocket/conn_browser.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index 83f07a2f52..dea6071dcb 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -272,17 +272,17 @@ func arrayBufferToBytes(buffer js.Value) []byte { func errorEventToError(val js.Value) error { var typ string - if gotType := val.Get("type"); gotType != js.Undefined() { + if gotType := val.Get("type"); !gotType.Equal(js.Undefined()) { typ = gotType.String() } else { typ = val.Type().String() } var reason string - if gotReason := val.Get("reason"); gotReason != js.Undefined() && gotReason.String() != "" { + if gotReason := val.Get("reason"); !gotReason.Equal(js.Undefined()) && gotReason.String() != "" { reason = gotReason.String() } else { code := val.Get("code") - if code != js.Undefined() { + if !code.Equal(js.Undefined()) { switch code := code.Int(); code { case 1006: reason = "code 1006: connection unexpectedly closed" From 94f5bc4aa6314474f19b9e00796ad5fab34aadd5 Mon Sep 17 00:00:00 2001 From: "M. Hawn" Date: Wed, 21 Oct 2020 00:37:13 -0600 Subject: [PATCH 72/93] Dependancy: Remove depricated multiaddr-net Switch to multiaddr/net --- p2p/transport/websocket/addrs.go | 2 +- p2p/transport/websocket/listener.go | 2 +- p2p/transport/websocket/websocket.go | 2 +- p2p/transport/websocket/websocket_browser.go | 4 ++-- p2p/transport/websocket/websocket_native.go | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/p2p/transport/websocket/addrs.go b/p2p/transport/websocket/addrs.go index e5dbc46e43..e789399da3 100644 --- a/p2p/transport/websocket/addrs.go +++ b/p2p/transport/websocket/addrs.go @@ -6,7 +6,7 @@ import ( "net/url" ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr-net" + manet "github.com/multiformats/go-multiaddr/net" ) // Addr is an implementation of net.Addr for WebSocket. diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go index 47fb8e7129..1af9ed428b 100644 --- a/p2p/transport/websocket/listener.go +++ b/p2p/transport/websocket/listener.go @@ -8,7 +8,7 @@ import ( "net/http" ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr-net" + manet "github.com/multiformats/go-multiaddr/net" ) type listener struct { diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index ddd4141090..5e7dc9989c 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -9,7 +9,7 @@ import ( tptu "github.com/libp2p/go-libp2p-transport-upgrader" ma "github.com/multiformats/go-multiaddr" mafmt "github.com/multiformats/go-multiaddr-fmt" - manet "github.com/multiformats/go-multiaddr-net" + manet "github.com/multiformats/go-multiaddr/net" ) // WsProtocol is the multiaddr protocol definition for this transport. diff --git a/p2p/transport/websocket/websocket_browser.go b/p2p/transport/websocket/websocket_browser.go index 258a82917c..c62a97d048 100644 --- a/p2p/transport/websocket/websocket_browser.go +++ b/p2p/transport/websocket/websocket_browser.go @@ -9,7 +9,7 @@ import ( "github.com/libp2p/go-libp2p-core/transport" ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr-net" + manet "github.com/multiformats/go-multiaddr/net" ) func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) { @@ -29,7 +29,7 @@ func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (ma conn.Close() return nil, err } - + return mnc, nil } diff --git a/p2p/transport/websocket/websocket_native.go b/p2p/transport/websocket/websocket_native.go index 86b872c02f..991f66e7b5 100644 --- a/p2p/transport/websocket/websocket_native.go +++ b/p2p/transport/websocket/websocket_native.go @@ -11,7 +11,7 @@ import ( ws "github.com/gorilla/websocket" "github.com/libp2p/go-libp2p-core/transport" ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr-net" + manet "github.com/multiformats/go-multiaddr/net" ) // Default gorilla upgrader From a0f24eb80262dbe35a32f1527444c596f918b7a4 Mon Sep 17 00:00:00 2001 From: "M. Hawn" Date: Wed, 21 Oct 2020 08:36:12 -0600 Subject: [PATCH 73/93] go fmt --- p2p/transport/websocket/websocket_browser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/websocket/websocket_browser.go b/p2p/transport/websocket/websocket_browser.go index c62a97d048..c76dd3ec6d 100644 --- a/p2p/transport/websocket/websocket_browser.go +++ b/p2p/transport/websocket/websocket_browser.go @@ -29,7 +29,7 @@ func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (ma conn.Close() return nil, err } - + return mnc, nil } From bc54bc258f61de9f1d3004af6255e8ea8d97bd38 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 17 Dec 2020 16:55:41 +0700 Subject: [PATCH 74/93] pass a context to OpenStream in tests --- p2p/transport/websocket/browser_integration_native_test.go | 3 ++- p2p/transport/websocket/websocket_test.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/p2p/transport/websocket/browser_integration_native_test.go b/p2p/transport/websocket/browser_integration_native_test.go index fa940b5c53..01486142b6 100644 --- a/p2p/transport/websocket/browser_integration_native_test.go +++ b/p2p/transport/websocket/browser_integration_native_test.go @@ -4,6 +4,7 @@ package websocket import ( "bufio" + "context" "os" "os/exec" "path/filepath" @@ -68,7 +69,7 @@ func TestInBrowser(t *testing.T) { t.Fatal("SERVER:", err) } defer conn.Close() - stream, err := conn.OpenStream() + stream, err := conn.OpenStream(context.Background()) if err != nil { t.Fatal("SERVER: could not open stream:", err) } diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index bada345232..aa51b74326 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -42,6 +42,7 @@ func TestCanDial(t *testing.T) { } func TestWebsocketTransport(t *testing.T) { + t.Skip("This test is failing, see https://github.com/libp2p/go-ws-transport/issues/99") ta := New(&tptu.Upgrader{ Secure: insecure.New("peerA"), Muxer: new(mplex.Transport), From 9cbaa8d2f03a1f16e2951c7741f071825b2702b8 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 14 Jul 2021 19:03:11 -0700 Subject: [PATCH 75/93] chore: various cleanups required to get vet/staticcheck/test to pass Importantly: this removes WsCodec (was exported). But it's deprecated and nobody references it anyways (we register it locally). --- .../websocket/browser_integration_native_test.go | 14 +++++++------- p2p/transport/websocket/tools/go.mod | 4 ++-- p2p/transport/websocket/tools/go.sum | 4 ++-- p2p/transport/websocket/websocket.go | 11 ++--------- p2p/transport/websocket/websocket_test.go | 4 ++-- 5 files changed, 15 insertions(+), 22 deletions(-) diff --git a/p2p/transport/websocket/browser_integration_native_test.go b/p2p/transport/websocket/browser_integration_native_test.go index 01486142b6..6934fb14f2 100644 --- a/p2p/transport/websocket/browser_integration_native_test.go +++ b/p2p/transport/websocket/browser_integration_native_test.go @@ -58,33 +58,33 @@ func TestInBrowser(t *testing.T) { }) addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") if err != nil { - t.Fatal("SERVER:", err) + t.Error("SERVER:", err) } listener, err := tpt.Listen(addr) if err != nil { - t.Fatal("SERVER:", err) + t.Error("SERVER:", err) } conn, err := listener.Accept() if err != nil { - t.Fatal("SERVER:", err) + t.Error("SERVER:", err) } defer conn.Close() stream, err := conn.OpenStream(context.Background()) if err != nil { - t.Fatal("SERVER: could not open stream:", err) + t.Error("SERVER: could not open stream:", err) } defer stream.Close() buf := bufio.NewReader(stream) if _, err := stream.Write([]byte("ping\n")); err != nil { - t.Fatal("SERVER:", err) + t.Error("SERVER:", err) } msg, err := buf.ReadString('\n') if err != nil { - t.Fatal("SERVER: could not read pong message:" + err.Error()) + t.Error("SERVER: could not read pong message:" + err.Error()) } expected := "pong\n" if msg != expected { - t.Fatalf("SERVER: Received wrong message. Expected %q but got %q", expected, msg) + t.Errorf("SERVER: Received wrong message. Expected %q but got %q", expected, msg) } }() diff --git a/p2p/transport/websocket/tools/go.mod b/p2p/transport/websocket/tools/go.mod index d34d7385b2..011649b66f 100644 --- a/p2p/transport/websocket/tools/go.mod +++ b/p2p/transport/websocket/tools/go.mod @@ -1,5 +1,5 @@ module github.com/libp2p/go-ws-transport/tools -go 1.13 +go 1.15 -require github.com/agnivade/wasmbrowsertest v0.3.1 +require github.com/agnivade/wasmbrowsertest v0.3.5 diff --git a/p2p/transport/websocket/tools/go.sum b/p2p/transport/websocket/tools/go.sum index 94b9c35ab1..164a43e812 100644 --- a/p2p/transport/websocket/tools/go.sum +++ b/p2p/transport/websocket/tools/go.sum @@ -1,5 +1,5 @@ -github.com/agnivade/wasmbrowsertest v0.3.1 h1:bA9aA+bcp7KuqGvmCuBdnMqy6PXxFjYP7FxsaT+JSqc= -github.com/agnivade/wasmbrowsertest v0.3.1/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI= +github.com/agnivade/wasmbrowsertest v0.3.5 h1:U8ICR7Xa3LBGQb57HtbbXf5KAMjQhiWrCWlr1kD77Cw= +github.com/agnivade/wasmbrowsertest v0.3.5/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI= github.com/chromedp/cdproto v0.0.0-20190614062957-d6d2f92b486d/go.mod h1:S8mB5wY3vV+vRIzf39xDXsw3XKYewW9X6rW2aEmkrSw= github.com/chromedp/cdproto v0.0.0-20190621002710-8cbd498dd7a0 h1:4Wocv9f+KWF4GtZudyrn8JSBTgHQbGp86mcsoH7j1iQ= github.com/chromedp/cdproto v0.0.0-20190621002710-8cbd498dd7a0/go.mod h1:S8mB5wY3vV+vRIzf39xDXsw3XKYewW9X6rW2aEmkrSw= diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 5e7dc9989c..77d522deb4 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -20,20 +20,13 @@ var WsProtocol = ma.ProtocolWithCode(ma.P_WS) // WsFmt is multiaddr formatter for WsProtocol var WsFmt = mafmt.And(mafmt.TCP, mafmt.Base(ma.P_WS)) -// WsCodec is the multiaddr-net codec definition for the websocket transport -var WsCodec = &manet.NetCodec{ - NetAddrNetworks: []string{"websocket"}, - ProtocolName: "ws", - ConvertMultiaddr: ConvertWebsocketMultiaddrToNetAddr, - ParseNetAddr: ParseWebsocketNetAddr, -} - // This is _not_ WsFmt because we want the transport to stick to dialing fully // resolved addresses. var dialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_TCP), mafmt.Base(ma.P_WS)) func init() { - manet.RegisterNetCodec(WsCodec) + manet.RegisterFromNetAddr(ParseWebsocketNetAddr, "websocket") + manet.RegisterToNetAddr(ConvertWebsocketMultiaddrToNetAddr, "ws") } var _ transport.Transport = (*WebsocketTransport)(nil) diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index aa51b74326..1082722e86 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -164,11 +164,11 @@ func TestWriteZero(t *testing.T) { go func() { c, err := tpt.maDial(context.Background(), l.Multiaddr()) - defer c.Close() if err != nil { t.Error(err) return } + defer c.Close() for i := 0; i < 100; i++ { n, err := c.Write(msg) @@ -183,10 +183,10 @@ func TestWriteZero(t *testing.T) { }() c, err := l.Accept() - defer c.Close() if err != nil { t.Fatal(err) } + defer c.Close() buf := make([]byte, 100) n, err := c.Read(buf) if n != 0 { From 7a309295132bf5c38734b01d8c132db7b09ce69f Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 15 Jul 2021 08:50:46 -0700 Subject: [PATCH 76/93] ci: make the "tools" directory a valid go package --- p2p/transport/websocket/tools/main.go | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 p2p/transport/websocket/tools/main.go diff --git a/p2p/transport/websocket/tools/main.go b/p2p/transport/websocket/tools/main.go new file mode 100644 index 0000000000..f9eebf1c07 --- /dev/null +++ b/p2p/transport/websocket/tools/main.go @@ -0,0 +1,2 @@ +// Makes this a valid go package, regardless of the build flags. +package tools From 3864f333411746382e44c6870044394ed9ce20bd Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 15 Jul 2021 09:01:35 -0700 Subject: [PATCH 77/93] test: disable wasm test on windows Doesn't work. --- p2p/transport/websocket/browser_integration_native_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/websocket/browser_integration_native_test.go b/p2p/transport/websocket/browser_integration_native_test.go index 6934fb14f2..90e438a232 100644 --- a/p2p/transport/websocket/browser_integration_native_test.go +++ b/p2p/transport/websocket/browser_integration_native_test.go @@ -1,4 +1,4 @@ -// +build !js +// +build !js,!windows package websocket From 6957d5782461828a8323ed53a0519c6735d848e6 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 22 Jul 2021 21:35:44 +0200 Subject: [PATCH 78/93] remove deprecated type --- p2p/transport/websocket/websocket.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 77d522deb4..49ace85bc0 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -12,11 +12,6 @@ import ( manet "github.com/multiformats/go-multiaddr/net" ) -// WsProtocol is the multiaddr protocol definition for this transport. -// -// Deprecated: use `ma.ProtocolWithCode(ma.P_WS) -var WsProtocol = ma.ProtocolWithCode(ma.P_WS) - // WsFmt is multiaddr formatter for WsProtocol var WsFmt = mafmt.And(mafmt.TCP, mafmt.Base(ma.P_WS)) @@ -45,7 +40,7 @@ func (t *WebsocketTransport) CanDial(a ma.Multiaddr) bool { } func (t *WebsocketTransport) Protocols() []int { - return []int{WsProtocol.Code} + return []int{ma.ProtocolWithCode(ma.P_WS).Code} } func (t *WebsocketTransport) Proxy() bool { From cdd2cf7e469bdca4e76063b056ea684ca37b550b Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 24 Jul 2021 19:10:23 +0200 Subject: [PATCH 79/93] chore: update go-libp2p-transport-upgrader and go-libp2p-core --- .../browser_integration_browser_test.go | 16 ++++++++++--- .../browser_integration_native_test.go | 3 +-- p2p/transport/websocket/muxer_test.go | 23 +++++++++++++++++++ p2p/transport/websocket/websocket_test.go | 5 ++-- 4 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 p2p/transport/websocket/muxer_test.go diff --git a/p2p/transport/websocket/browser_integration_browser_test.go b/p2p/transport/websocket/browser_integration_browser_test.go index 6a55c30275..4c1b187ba1 100644 --- a/p2p/transport/websocket/browser_integration_browser_test.go +++ b/p2p/transport/websocket/browser_integration_browser_test.go @@ -8,22 +8,32 @@ import ( "testing" "time" - "github.com/libp2p/go-libp2p-core/sec/insecure" + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/test" mplex "github.com/libp2p/go-libp2p-mplex" tptu "github.com/libp2p/go-libp2p-transport-upgrader" ma "github.com/multiformats/go-multiaddr" ) func TestInBrowser(t *testing.T) { + priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256) + if err != nil { + t.Fatal(err) + } + id, err := peer.IDFromPrivateKey(priv) + if err != nil { + t.Fatal(err) + } tpt := New(&tptu.Upgrader{ - Secure: insecure.New("browserPeer"), + Secure: newSecureMuxer(t, id), Muxer: new(mplex.Transport), }) addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") if err != nil { t.Fatal("could not parse multiaddress:" + err.Error()) } - conn, err := tpt.Dial(context.Background(), addr, "serverPeer") + conn, err := tpt.Dial(context.Background(), addr, id) if err != nil { t.Fatal("could not dial server:" + err.Error()) } diff --git a/p2p/transport/websocket/browser_integration_native_test.go b/p2p/transport/websocket/browser_integration_native_test.go index 90e438a232..72690845c1 100644 --- a/p2p/transport/websocket/browser_integration_native_test.go +++ b/p2p/transport/websocket/browser_integration_native_test.go @@ -11,7 +11,6 @@ import ( "strings" "testing" - "github.com/libp2p/go-libp2p-core/sec/insecure" mplex "github.com/libp2p/go-libp2p-mplex" tptu "github.com/libp2p/go-libp2p-transport-upgrader" ma "github.com/multiformats/go-multiaddr" @@ -53,7 +52,7 @@ func TestInBrowser(t *testing.T) { close(serverDoneSignal) }() tpt := New(&tptu.Upgrader{ - Secure: insecure.New("serverPeer"), + Secure: newSecureMuxer(t, "serverPeer"), Muxer: new(mplex.Transport), }) addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") diff --git a/p2p/transport/websocket/muxer_test.go b/p2p/transport/websocket/muxer_test.go new file mode 100644 index 0000000000..8042c183db --- /dev/null +++ b/p2p/transport/websocket/muxer_test.go @@ -0,0 +1,23 @@ +package websocket + +import ( + "testing" + + csms "github.com/libp2p/go-conn-security-multistream" + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/sec" + "github.com/libp2p/go-libp2p-core/sec/insecure" + "github.com/libp2p/go-libp2p-core/test" +) + +func newSecureMuxer(t *testing.T, id peer.ID) sec.SecureMuxer { + t.Helper() + priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256) + if err != nil { + t.Fatal(err) + } + var secMuxer csms.SSMuxer + secMuxer.AddTransport(insecure.ID, insecure.NewWithIdentity(id, priv)) + return &secMuxer +} diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index 1082722e86..aa827e81fd 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -10,7 +10,6 @@ import ( "testing" "testing/iotest" - "github.com/libp2p/go-libp2p-core/sec/insecure" mplex "github.com/libp2p/go-libp2p-mplex" ttransport "github.com/libp2p/go-libp2p-testing/suites/transport" tptu "github.com/libp2p/go-libp2p-transport-upgrader" @@ -44,11 +43,11 @@ func TestCanDial(t *testing.T) { func TestWebsocketTransport(t *testing.T) { t.Skip("This test is failing, see https://github.com/libp2p/go-ws-transport/issues/99") ta := New(&tptu.Upgrader{ - Secure: insecure.New("peerA"), + Secure: newSecureMuxer(t, "peerA"), Muxer: new(mplex.Transport), }) tb := New(&tptu.Upgrader{ - Secure: insecure.New("peerB"), + Secure: newSecureMuxer(t, "peerB"), Muxer: new(mplex.Transport), }) From 96471694fe095e909810c43c7b98c590161fb0e9 Mon Sep 17 00:00:00 2001 From: web3-bot <81333946+web3-bot@users.noreply.github.com> Date: Tue, 17 Aug 2021 10:46:37 -0400 Subject: [PATCH 80/93] sync: update CI config files (#106) --- p2p/transport/websocket/browser_integration_browser_test.go | 1 + p2p/transport/websocket/browser_integration_native_test.go | 1 + p2p/transport/websocket/conn_browser.go | 1 + p2p/transport/websocket/conn_native.go | 1 + p2p/transport/websocket/listener.go | 1 + p2p/transport/websocket/tools/go.mod | 2 +- p2p/transport/websocket/tools/tools.go | 1 + p2p/transport/websocket/websocket_browser.go | 1 + p2p/transport/websocket/websocket_native.go | 1 + p2p/transport/websocket/websocket_test.go | 1 + 10 files changed, 10 insertions(+), 1 deletion(-) diff --git a/p2p/transport/websocket/browser_integration_browser_test.go b/p2p/transport/websocket/browser_integration_browser_test.go index 4c1b187ba1..45c9a7c5e2 100644 --- a/p2p/transport/websocket/browser_integration_browser_test.go +++ b/p2p/transport/websocket/browser_integration_browser_test.go @@ -1,3 +1,4 @@ +//go:build js && wasm // +build js,wasm package websocket diff --git a/p2p/transport/websocket/browser_integration_native_test.go b/p2p/transport/websocket/browser_integration_native_test.go index 72690845c1..42772c1d97 100644 --- a/p2p/transport/websocket/browser_integration_native_test.go +++ b/p2p/transport/websocket/browser_integration_native_test.go @@ -1,3 +1,4 @@ +//go:build !js && !windows // +build !js,!windows package websocket diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go index dea6071dcb..eaf0e645eb 100644 --- a/p2p/transport/websocket/conn_browser.go +++ b/p2p/transport/websocket/conn_browser.go @@ -1,3 +1,4 @@ +//go:build js && wasm // +build js,wasm package websocket diff --git a/p2p/transport/websocket/conn_native.go b/p2p/transport/websocket/conn_native.go index 7a0daeacaf..37ff31edb4 100644 --- a/p2p/transport/websocket/conn_native.go +++ b/p2p/transport/websocket/conn_native.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package websocket diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go index 1af9ed428b..8931b1f17c 100644 --- a/p2p/transport/websocket/listener.go +++ b/p2p/transport/websocket/listener.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package websocket diff --git a/p2p/transport/websocket/tools/go.mod b/p2p/transport/websocket/tools/go.mod index 011649b66f..d295a7a3bd 100644 --- a/p2p/transport/websocket/tools/go.mod +++ b/p2p/transport/websocket/tools/go.mod @@ -1,5 +1,5 @@ module github.com/libp2p/go-ws-transport/tools -go 1.15 +go 1.16 require github.com/agnivade/wasmbrowsertest v0.3.5 diff --git a/p2p/transport/websocket/tools/tools.go b/p2p/transport/websocket/tools/tools.go index d051c510b9..f4c550d7bd 100644 --- a/p2p/transport/websocket/tools/tools.go +++ b/p2p/transport/websocket/tools/tools.go @@ -1,3 +1,4 @@ +//go:build tools // +build tools package tools diff --git a/p2p/transport/websocket/websocket_browser.go b/p2p/transport/websocket/websocket_browser.go index c76dd3ec6d..eef1f981da 100644 --- a/p2p/transport/websocket/websocket_browser.go +++ b/p2p/transport/websocket/websocket_browser.go @@ -1,3 +1,4 @@ +//go:build js && wasm // +build js,wasm package websocket diff --git a/p2p/transport/websocket/websocket_native.go b/p2p/transport/websocket/websocket_native.go index 991f66e7b5..b6e95a3405 100644 --- a/p2p/transport/websocket/websocket_native.go +++ b/p2p/transport/websocket/websocket_native.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package websocket diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index aa827e81fd..3f904f92c0 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -1,3 +1,4 @@ +//go:build !js // +build !js package websocket From 75dda2b07a8ed140c2d4af9ad43c5d1a88c8634c Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 30 Dec 2021 17:07:53 +0400 Subject: [PATCH 81/93] update deps and fix deprecated calls --- p2p/transport/websocket/websocket.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 49ace85bc0..2af9d3439e 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -4,9 +4,12 @@ package websocket import ( "context" + "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/transport" + tptu "github.com/libp2p/go-libp2p-transport-upgrader" + ma "github.com/multiformats/go-multiaddr" mafmt "github.com/multiformats/go-multiaddr-fmt" manet "github.com/multiformats/go-multiaddr/net" @@ -52,5 +55,5 @@ func (t *WebsocketTransport) Dial(ctx context.Context, raddr ma.Multiaddr, p pee if err != nil { return nil, err } - return t.Upgrader.UpgradeOutbound(ctx, t, macon, p) + return t.Upgrader.Upgrade(ctx, t, macon, network.DirOutbound, p) } From 081817c97814d416319d040b0aeb1cb7983ed025 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 2 Jan 2022 16:13:00 +0400 Subject: [PATCH 82/93] use the transport.Upgrader interface --- .../browser_integration_browser_test.go | 9 +++++---- .../browser_integration_native_test.go | 9 +++++---- p2p/transport/websocket/websocket.go | 6 ++---- p2p/transport/websocket/websocket_test.go | 18 ++++++++++-------- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/p2p/transport/websocket/browser_integration_browser_test.go b/p2p/transport/websocket/browser_integration_browser_test.go index 45c9a7c5e2..6abc9da9e9 100644 --- a/p2p/transport/websocket/browser_integration_browser_test.go +++ b/p2p/transport/websocket/browser_integration_browser_test.go @@ -26,10 +26,11 @@ func TestInBrowser(t *testing.T) { if err != nil { t.Fatal(err) } - tpt := New(&tptu.Upgrader{ - Secure: newSecureMuxer(t, id), - Muxer: new(mplex.Transport), - }) + u, err := tptu.New(newSecureMuxer(t, id), new(mplex.Transport)) + if err != nil { + t.Fatal(err) + } + tpt := New(u) addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") if err != nil { t.Fatal("could not parse multiaddress:" + err.Error()) diff --git a/p2p/transport/websocket/browser_integration_native_test.go b/p2p/transport/websocket/browser_integration_native_test.go index 42772c1d97..a817951a9d 100644 --- a/p2p/transport/websocket/browser_integration_native_test.go +++ b/p2p/transport/websocket/browser_integration_native_test.go @@ -52,10 +52,11 @@ func TestInBrowser(t *testing.T) { defer func() { close(serverDoneSignal) }() - tpt := New(&tptu.Upgrader{ - Secure: newSecureMuxer(t, "serverPeer"), - Muxer: new(mplex.Transport), - }) + u, err := tptu.New(newSecureMuxer(t, "serverPeer"), new(mplex.Transport)) + if err != nil { + t.Error("SERVER:", err) + } + tpt := New(u) addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") if err != nil { t.Error("SERVER:", err) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 2af9d3439e..444bc34983 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -8,8 +8,6 @@ import ( "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/transport" - tptu "github.com/libp2p/go-libp2p-transport-upgrader" - ma "github.com/multiformats/go-multiaddr" mafmt "github.com/multiformats/go-multiaddr-fmt" manet "github.com/multiformats/go-multiaddr/net" @@ -31,10 +29,10 @@ var _ transport.Transport = (*WebsocketTransport)(nil) // WebsocketTransport is the actual go-libp2p transport type WebsocketTransport struct { - Upgrader *tptu.Upgrader + Upgrader transport.Upgrader } -func New(u *tptu.Upgrader) *WebsocketTransport { +func New(u transport.Upgrader) *WebsocketTransport { return &WebsocketTransport{u} } diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index 3f904f92c0..e6dd710e47 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -43,14 +43,16 @@ func TestCanDial(t *testing.T) { func TestWebsocketTransport(t *testing.T) { t.Skip("This test is failing, see https://github.com/libp2p/go-ws-transport/issues/99") - ta := New(&tptu.Upgrader{ - Secure: newSecureMuxer(t, "peerA"), - Muxer: new(mplex.Transport), - }) - tb := New(&tptu.Upgrader{ - Secure: newSecureMuxer(t, "peerB"), - Muxer: new(mplex.Transport), - }) + ua, err := tptu.New(newSecureMuxer(t, "peerA"), new(mplex.Transport)) + if err != nil { + t.Fatal(err) + } + ta := New(ua) + ub, err := tptu.New(newSecureMuxer(t, "peerB"), new(mplex.Transport)) + if err != nil { + t.Fatal(err) + } + tb := New(ub) zero := "/ip4/127.0.0.1/tcp/0/ws" ttransport.SubtestTransport(t, ta, tb, zero, "peerA") From b074ba43281839f326e55c325db7e44006afbab7 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 4 Jan 2022 16:59:46 +0400 Subject: [PATCH 83/93] unexport WebsocketTransport.Upgrader --- p2p/transport/websocket/websocket.go | 4 ++-- p2p/transport/websocket/websocket_native.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 444bc34983..279a90a3f3 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -29,7 +29,7 @@ var _ transport.Transport = (*WebsocketTransport)(nil) // WebsocketTransport is the actual go-libp2p transport type WebsocketTransport struct { - Upgrader transport.Upgrader + upgrader transport.Upgrader } func New(u transport.Upgrader) *WebsocketTransport { @@ -53,5 +53,5 @@ func (t *WebsocketTransport) Dial(ctx context.Context, raddr ma.Multiaddr, p pee if err != nil { return nil, err } - return t.Upgrader.Upgrade(ctx, t, macon, network.DirOutbound, p) + return t.upgrader.Upgrade(ctx, t, macon, network.DirOutbound, p) } diff --git a/p2p/transport/websocket/websocket_native.go b/p2p/transport/websocket/websocket_native.go index b6e95a3405..b652045e89 100644 --- a/p2p/transport/websocket/websocket_native.go +++ b/p2p/transport/websocket/websocket_native.go @@ -75,7 +75,7 @@ func (t *WebsocketTransport) Listen(a ma.Multiaddr) (transport.Listener, error) if err != nil { return nil, err } - return t.Upgrader.UpgradeListener(t, malist), nil + return t.upgrader.UpgradeListener(t, malist), nil } func (t *WebsocketTransport) wrapListener(l net.Listener, origin *url.URL) (*listener, error) { From abde7f483bdfa7b0d5e2f125d46ef197f110451c Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 4 Jan 2022 17:06:11 +0400 Subject: [PATCH 84/93] use the resource manager --- .../browser_integration_browser_test.go | 2 +- .../browser_integration_native_test.go | 2 +- p2p/transport/websocket/websocket.go | 18 +++++++++++++++--- p2p/transport/websocket/websocket_test.go | 4 ++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/p2p/transport/websocket/browser_integration_browser_test.go b/p2p/transport/websocket/browser_integration_browser_test.go index 6abc9da9e9..f8405373b9 100644 --- a/p2p/transport/websocket/browser_integration_browser_test.go +++ b/p2p/transport/websocket/browser_integration_browser_test.go @@ -30,7 +30,7 @@ func TestInBrowser(t *testing.T) { if err != nil { t.Fatal(err) } - tpt := New(u) + tpt := New(u, nil) addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") if err != nil { t.Fatal("could not parse multiaddress:" + err.Error()) diff --git a/p2p/transport/websocket/browser_integration_native_test.go b/p2p/transport/websocket/browser_integration_native_test.go index a817951a9d..3e7410b46e 100644 --- a/p2p/transport/websocket/browser_integration_native_test.go +++ b/p2p/transport/websocket/browser_integration_native_test.go @@ -56,7 +56,7 @@ func TestInBrowser(t *testing.T) { if err != nil { t.Error("SERVER:", err) } - tpt := New(u) + tpt := New(u, nil) addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") if err != nil { t.Error("SERVER:", err) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 279a90a3f3..a620904a67 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -30,10 +30,17 @@ var _ transport.Transport = (*WebsocketTransport)(nil) // WebsocketTransport is the actual go-libp2p transport type WebsocketTransport struct { upgrader transport.Upgrader + rcmgr network.ResourceManager } -func New(u transport.Upgrader) *WebsocketTransport { - return &WebsocketTransport{u} +func New(u transport.Upgrader, rcmgr network.ResourceManager) *WebsocketTransport { + if rcmgr == nil { + rcmgr = network.NullResourceManager + } + return &WebsocketTransport{ + upgrader: u, + rcmgr: rcmgr, + } } func (t *WebsocketTransport) CanDial(a ma.Multiaddr) bool { @@ -49,9 +56,14 @@ func (t *WebsocketTransport) Proxy() bool { } func (t *WebsocketTransport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (transport.CapableConn, error) { + connScope, err := t.rcmgr.OpenConnection(network.DirOutbound, true) + if err != nil { + return nil, err + } macon, err := t.maDial(ctx, raddr) if err != nil { + connScope.Done() return nil, err } - return t.upgrader.Upgrade(ctx, t, macon, network.DirOutbound, p) + return t.upgrader.Upgrade(ctx, t, macon, network.DirOutbound, p, connScope) } diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index e6dd710e47..a9ac9183c6 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -47,12 +47,12 @@ func TestWebsocketTransport(t *testing.T) { if err != nil { t.Fatal(err) } - ta := New(ua) + ta := New(ua, nil) ub, err := tptu.New(newSecureMuxer(t, "peerB"), new(mplex.Transport)) if err != nil { t.Fatal(err) } - tb := New(ub) + tb := New(ub, nil) zero := "/ip4/127.0.0.1/tcp/0/ws" ttransport.SubtestTransport(t, ta, tb, zero, "peerA") From 2d53c8f4ebb7aa401a4e855f4668fb121d3fe7b1 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 14 Feb 2022 12:10:52 +0530 Subject: [PATCH 85/93] remove wasm support (#114) --- .../browser_integration_browser_test.go | 71 ---- .../browser_integration_native_test.go | 109 ------- p2p/transport/websocket/conn.go | 137 ++++++++ p2p/transport/websocket/conn_browser.go | 305 ------------------ p2p/transport/websocket/conn_native.go | 146 --------- p2p/transport/websocket/listener.go | 3 - p2p/transport/websocket/muxer_test.go | 23 -- p2p/transport/websocket/tools/go.mod | 5 - p2p/transport/websocket/tools/go.sum | 31 -- p2p/transport/websocket/tools/main.go | 2 - p2p/transport/websocket/tools/tools.go | 6 - p2p/transport/websocket/websocket.go | 88 ++++- p2p/transport/websocket/websocket_browser.go | 39 --- p2p/transport/websocket/websocket_native.go | 98 ------ p2p/transport/websocket/websocket_test.go | 22 +- 15 files changed, 243 insertions(+), 842 deletions(-) delete mode 100644 p2p/transport/websocket/browser_integration_browser_test.go delete mode 100644 p2p/transport/websocket/browser_integration_native_test.go delete mode 100644 p2p/transport/websocket/conn_browser.go delete mode 100644 p2p/transport/websocket/conn_native.go delete mode 100644 p2p/transport/websocket/muxer_test.go delete mode 100644 p2p/transport/websocket/tools/go.mod delete mode 100644 p2p/transport/websocket/tools/go.sum delete mode 100644 p2p/transport/websocket/tools/main.go delete mode 100644 p2p/transport/websocket/tools/tools.go delete mode 100644 p2p/transport/websocket/websocket_browser.go delete mode 100644 p2p/transport/websocket/websocket_native.go diff --git a/p2p/transport/websocket/browser_integration_browser_test.go b/p2p/transport/websocket/browser_integration_browser_test.go deleted file mode 100644 index f8405373b9..0000000000 --- a/p2p/transport/websocket/browser_integration_browser_test.go +++ /dev/null @@ -1,71 +0,0 @@ -//go:build js && wasm -// +build js,wasm - -package websocket - -import ( - "bufio" - "context" - "testing" - "time" - - "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/test" - mplex "github.com/libp2p/go-libp2p-mplex" - tptu "github.com/libp2p/go-libp2p-transport-upgrader" - ma "github.com/multiformats/go-multiaddr" -) - -func TestInBrowser(t *testing.T) { - priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256) - if err != nil { - t.Fatal(err) - } - id, err := peer.IDFromPrivateKey(priv) - if err != nil { - t.Fatal(err) - } - u, err := tptu.New(newSecureMuxer(t, id), new(mplex.Transport)) - if err != nil { - t.Fatal(err) - } - tpt := New(u, nil) - addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") - if err != nil { - t.Fatal("could not parse multiaddress:" + err.Error()) - } - conn, err := tpt.Dial(context.Background(), addr, id) - if err != nil { - t.Fatal("could not dial server:" + err.Error()) - } - defer conn.Close() - - stream, err := conn.AcceptStream() - if err != nil { - t.Fatal("could not accept stream:" + err.Error()) - } - defer stream.Close() - - buf := bufio.NewReader(stream) - msg, err := buf.ReadString('\n') - if err != nil { - t.Fatal("could not read ping message:" + err.Error()) - } - expected := "ping\n" - if msg != expected { - t.Fatalf("Received wrong message. Expected %q but got %q", expected, msg) - } - - _, err = stream.Write([]byte("pong\n")) - if err != nil { - t.Fatal("could not write pong message:" + err.Error()) - } - - // TODO(albrow): This hack is necessary in order to give the reader time to - // finish. As soon as this test function returns, the browser window is - // closed, which means there is no time for the other end of the connection to - // read the "pong" message. We should find some way to remove this hack if - // possible. - time.Sleep(1 * time.Second) -} diff --git a/p2p/transport/websocket/browser_integration_native_test.go b/p2p/transport/websocket/browser_integration_native_test.go deleted file mode 100644 index 3e7410b46e..0000000000 --- a/p2p/transport/websocket/browser_integration_native_test.go +++ /dev/null @@ -1,109 +0,0 @@ -//go:build !js && !windows -// +build !js,!windows - -package websocket - -import ( - "bufio" - "context" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - - mplex "github.com/libp2p/go-libp2p-mplex" - tptu "github.com/libp2p/go-libp2p-transport-upgrader" - ma "github.com/multiformats/go-multiaddr" -) - -var ( - wasmBrowserTestBin = "wasmbrowsertest" - wasmBrowserTestDir = filepath.Join("tools", "bin") - wasmBrowserTestPackage = "github.com/agnivade/wasmbrowsertest" -) - -// TestInBrowser is a harness that allows us to use `go test` in order to run -// WebAssembly tests in a headless browser. -func TestInBrowser(t *testing.T) { - // ensure we have the right tools. - err := os.MkdirAll(wasmBrowserTestDir, 0755) - - t.Logf("building %s", wasmBrowserTestPackage) - if err != nil && !os.IsExist(err) { - t.Fatal(err) - } - - cmd := exec.Command( - "go", "build", - "-o", wasmBrowserTestBin, - "github.com/agnivade/wasmbrowsertest", - ) - cmd.Dir = wasmBrowserTestDir - err = cmd.Run() - if err != nil { - t.Fatal(err) - } - t.Log("starting server") - - // Start a transport which the browser peer will dial. - serverDoneSignal := make(chan struct{}) - go func() { - defer func() { - close(serverDoneSignal) - }() - u, err := tptu.New(newSecureMuxer(t, "serverPeer"), new(mplex.Transport)) - if err != nil { - t.Error("SERVER:", err) - } - tpt := New(u, nil) - addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") - if err != nil { - t.Error("SERVER:", err) - } - listener, err := tpt.Listen(addr) - if err != nil { - t.Error("SERVER:", err) - } - conn, err := listener.Accept() - if err != nil { - t.Error("SERVER:", err) - } - defer conn.Close() - stream, err := conn.OpenStream(context.Background()) - if err != nil { - t.Error("SERVER: could not open stream:", err) - } - defer stream.Close() - buf := bufio.NewReader(stream) - if _, err := stream.Write([]byte("ping\n")); err != nil { - t.Error("SERVER:", err) - } - msg, err := buf.ReadString('\n') - if err != nil { - t.Error("SERVER: could not read pong message:" + err.Error()) - } - expected := "pong\n" - if msg != expected { - t.Errorf("SERVER: Received wrong message. Expected %q but got %q", expected, msg) - } - }() - - t.Log("starting browser") - - cmd = exec.Command( - "go", "test", "-v", - "-exec", filepath.Join(wasmBrowserTestDir, wasmBrowserTestBin), - "-run", "TestInBrowser", - ".", - ) - cmd.Env = append(os.Environ(), []string{"GOOS=js", "GOARCH=wasm"}...) - output, err := cmd.CombinedOutput() - if err != nil { - formattedOutput := "\t" + strings.Join(strings.Split(string(output), "\n"), "\n\t") - t.Log("BROWSER OUTPUT:\n", formattedOutput) - t.Fatal("BROWSER:", err) - } - - <-serverDoneSignal -} diff --git a/p2p/transport/websocket/conn.go b/p2p/transport/websocket/conn.go index b100b44598..5f520a1e76 100644 --- a/p2p/transport/websocket/conn.go +++ b/p2p/transport/websocket/conn.go @@ -1,12 +1,149 @@ package websocket import ( + "io" "net" + "sync" "time" + + ws "github.com/gorilla/websocket" ) // GracefulCloseTimeout is the time to wait trying to gracefully close a // connection before simply cutting it. var GracefulCloseTimeout = 100 * time.Millisecond +// Conn implements net.Conn interface for gorilla/websocket. +type Conn struct { + *ws.Conn + DefaultMessageType int + reader io.Reader + closeOnce sync.Once + + readLock, writeLock sync.Mutex +} + var _ net.Conn = (*Conn)(nil) + +func (c *Conn) Read(b []byte) (int, error) { + c.readLock.Lock() + defer c.readLock.Unlock() + + if c.reader == nil { + if err := c.prepNextReader(); err != nil { + return 0, err + } + } + + for { + n, err := c.reader.Read(b) + switch err { + case io.EOF: + c.reader = nil + + if n > 0 { + return n, nil + } + + if err := c.prepNextReader(); err != nil { + return 0, err + } + + // explicitly looping + default: + return n, err + } + } +} + +func (c *Conn) prepNextReader() error { + t, r, err := c.Conn.NextReader() + if err != nil { + if wserr, ok := err.(*ws.CloseError); ok { + if wserr.Code == 1000 || wserr.Code == 1005 { + return io.EOF + } + } + return err + } + + if t == ws.CloseMessage { + return io.EOF + } + + c.reader = r + return nil +} + +func (c *Conn) Write(b []byte) (n int, err error) { + c.writeLock.Lock() + defer c.writeLock.Unlock() + + if err := c.Conn.WriteMessage(c.DefaultMessageType, b); err != nil { + return 0, err + } + + return len(b), nil +} + +// Close closes the connection. Only the first call to Close will receive the +// close error, subsequent and concurrent calls will return nil. +// This method is thread-safe. +func (c *Conn) Close() error { + var err error + c.closeOnce.Do(func() { + err1 := c.Conn.WriteControl( + ws.CloseMessage, + ws.FormatCloseMessage(ws.CloseNormalClosure, "closed"), + time.Now().Add(GracefulCloseTimeout), + ) + err2 := c.Conn.Close() + switch { + case err1 != nil: + err = err1 + case err2 != nil: + err = err2 + } + }) + return err +} + +func (c *Conn) LocalAddr() net.Addr { + return NewAddr(c.Conn.LocalAddr().String()) +} + +func (c *Conn) RemoteAddr() net.Addr { + return NewAddr(c.Conn.RemoteAddr().String()) +} + +func (c *Conn) SetDeadline(t time.Time) error { + if err := c.SetReadDeadline(t); err != nil { + return err + } + + return c.SetWriteDeadline(t) +} + +func (c *Conn) SetReadDeadline(t time.Time) error { + // Don't lock when setting the read deadline. That would prevent us from + // interrupting an in-progress read. + return c.Conn.SetReadDeadline(t) +} + +func (c *Conn) SetWriteDeadline(t time.Time) error { + // Unlike the read deadline, we need to lock when setting the write + // deadline. + + c.writeLock.Lock() + defer c.writeLock.Unlock() + + return c.Conn.SetWriteDeadline(t) +} + +// NewConn creates a Conn given a regular gorilla/websocket Conn. +func NewConn(raw *ws.Conn) *Conn { + return &Conn{ + Conn: raw, + DefaultMessageType: ws.BinaryMessage, + } +} diff --git a/p2p/transport/websocket/conn_browser.go b/p2p/transport/websocket/conn_browser.go deleted file mode 100644 index eaf0e645eb..0000000000 --- a/p2p/transport/websocket/conn_browser.go +++ /dev/null @@ -1,305 +0,0 @@ -//go:build js && wasm -// +build js,wasm - -package websocket - -import ( - "bytes" - "errors" - "fmt" - "net" - "strings" - "sync" - "syscall/js" - "time" -) - -const ( - webSocketStateConnecting = 0 - webSocketStateOpen = 1 - webSocketStateClosing = 2 - webSocketStateClosed = 3 -) - -var errConnectionClosed = errors.New("connection is closed") - -// Conn implements net.Conn interface for WebSockets in js/wasm. -type Conn struct { - js.Value - messageHandler *js.Func - closeHandler *js.Func - errorHandler *js.Func - mut sync.Mutex - currDataMut sync.RWMutex - currData bytes.Buffer - closeOnce sync.Once - closeSignalOnce sync.Once - closeSignal chan struct{} - dataSignal chan struct{} - localAddr net.Addr - remoteAddr net.Addr - firstErr error // only read this _after_ observing that closeSignal has been closed. -} - -// NewConn creates a Conn given a regular js/wasm WebSocket Conn. -func NewConn(raw js.Value) *Conn { - conn := &Conn{ - Value: raw, - closeSignal: make(chan struct{}), - dataSignal: make(chan struct{}, 1), - localAddr: NewAddr("0.0.0.0:0"), - remoteAddr: getRemoteAddr(raw), - } - // Force the JavaScript WebSockets API to use the ArrayBuffer type for - // incoming messages instead of the Blob type. This is better for us because - // ArrayBuffer can be converted to []byte synchronously but Blob cannot. - conn.Set("binaryType", "arraybuffer") - conn.setUpHandlers() - return conn -} - -func (c *Conn) Read(b []byte) (int, error) { - select { - case <-c.closeSignal: - c.readAfterErr(b) - default: - } - - for { - c.currDataMut.RLock() - n, _ := c.currData.Read(b) - c.currDataMut.RUnlock() - - if n != 0 { - // Data was ready. Return the number of bytes read. - return n, nil - } - - // There is no data ready to be read. Wait for more data or for the - // connection to be closed. - select { - case <-c.dataSignal: - case <-c.closeSignal: - return c.readAfterErr(b) - } - } -} - -// readAfterError reads from c.currData. If there is no more data left it -// returns c.firstErr if non-nil and otherwise returns io.EOF. -func (c *Conn) readAfterErr(b []byte) (int, error) { - if c.firstErr != nil { - return 0, c.firstErr - } - c.currDataMut.RLock() - n, err := c.currData.Read(b) - c.currDataMut.RUnlock() - return n, err -} - -// checkOpen returns an error if the connection is not open. Otherwise, it -// returns nil. -func (c *Conn) checkOpen() error { - state := c.Get("readyState").Int() - switch state { - case webSocketStateClosed, webSocketStateClosing: - return errConnectionClosed - } - return nil -} - -func (c *Conn) Write(b []byte) (n int, err error) { - defer func() { - if e := recover(); e != nil { - err = recoveredValueToError(e) - } - }() - if err := c.checkOpen(); err != nil { - return 0, err - } - uint8Array := js.Global().Get("Uint8Array").New(len(b)) - if js.CopyBytesToJS(uint8Array, b) != len(b) { - panic("expected to copy all bytes") - } - c.Call("send", uint8Array.Get("buffer")) - return len(b), nil -} - -// Close closes the connection. Only the first call to Close will receive the -// close error, subsequent and concurrent calls will return nil. -// This method is thread-safe. -func (c *Conn) Close() error { - c.closeOnce.Do(func() { - c.Call("close") - c.signalClose(nil) - c.releaseHandlers() - }) - return nil -} - -func (c *Conn) signalClose(err error) { - c.closeSignalOnce.Do(func() { - c.firstErr = err - close(c.closeSignal) - }) -} - -func (c *Conn) releaseHandlers() { - c.mut.Lock() - defer c.mut.Unlock() - if c.messageHandler != nil { - c.Call("removeEventListener", "message", *c.messageHandler) - c.messageHandler.Release() - c.messageHandler = nil - } - if c.closeHandler != nil { - c.Call("removeEventListener", "close", *c.closeHandler) - c.closeHandler.Release() - c.closeHandler = nil - } - if c.errorHandler != nil { - c.Call("removeEventListener", "error", *c.errorHandler) - c.errorHandler.Release() - c.errorHandler = nil - } -} - -func (c *Conn) LocalAddr() net.Addr { - return c.localAddr -} - -func getRemoteAddr(val js.Value) net.Addr { - rawURL := val.Get("url").String() - withoutPrefix := strings.TrimPrefix(rawURL, "ws://") - withoutSuffix := strings.TrimSuffix(withoutPrefix, "/") - return NewAddr(withoutSuffix) -} - -func (c *Conn) RemoteAddr() net.Addr { - return c.remoteAddr -} - -// TODO: Return os.ErrNoDeadline. For now we return nil because multiplexers -// don't handle the error correctly. -func (c *Conn) SetDeadline(t time.Time) error { - return nil -} - -func (c *Conn) SetReadDeadline(t time.Time) error { - return nil -} - -func (c *Conn) SetWriteDeadline(t time.Time) error { - return nil -} - -func (c *Conn) setUpHandlers() { - c.mut.Lock() - defer c.mut.Unlock() - if c.messageHandler != nil { - // Message handlers already created. Nothing to do. - return - } - messageHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - arrayBuffer := args[0].Get("data") - data := arrayBufferToBytes(arrayBuffer) - c.currDataMut.Lock() - if _, err := c.currData.Write(data); err != nil { - c.currDataMut.Unlock() - return err - } - c.currDataMut.Unlock() - - // Non-blocking signal - select { - case c.dataSignal <- struct{}{}: - default: - } - - return nil - }) - c.messageHandler = &messageHandler - c.Call("addEventListener", "message", messageHandler) - - closeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - go func() { - c.signalClose(errorEventToError(args[0])) - c.releaseHandlers() - }() - return nil - }) - c.closeHandler = &closeHandler - c.Call("addEventListener", "close", closeHandler) - - errorHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - // Unfortunately, the "error" event doesn't appear to give us any useful - // information. All we can do is close the connection. - c.Close() - return nil - }) - c.errorHandler = &errorHandler - c.Call("addEventListener", "error", errorHandler) -} - -func (c *Conn) waitForOpen() error { - openSignal := make(chan struct{}) - handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - close(openSignal) - return nil - }) - defer c.Call("removeEventListener", "open", handler) - defer handler.Release() - c.Call("addEventListener", "open", handler) - select { - case <-openSignal: - return nil - case <-c.closeSignal: - // c.closeSignal means there was an error when trying to open the - // connection. - return c.firstErr - } -} - -// arrayBufferToBytes converts a JavaScript ArrayBuffer to a slice of bytes. -func arrayBufferToBytes(buffer js.Value) []byte { - view := js.Global().Get("Uint8Array").New(buffer) - dataLen := view.Length() - data := make([]byte, dataLen) - if js.CopyBytesToGo(data, view) != dataLen { - panic("expected to copy all bytes") - } - return data -} - -func errorEventToError(val js.Value) error { - var typ string - if gotType := val.Get("type"); !gotType.Equal(js.Undefined()) { - typ = gotType.String() - } else { - typ = val.Type().String() - } - var reason string - if gotReason := val.Get("reason"); !gotReason.Equal(js.Undefined()) && gotReason.String() != "" { - reason = gotReason.String() - } else { - code := val.Get("code") - if !code.Equal(js.Undefined()) { - switch code := code.Int(); code { - case 1006: - reason = "code 1006: connection unexpectedly closed" - default: - reason = fmt.Sprintf("unexpected code: %d", code) - } - } - } - return fmt.Errorf("JavaScript error: (%s) %s", typ, reason) -} - -func recoveredValueToError(e interface{}) error { - switch e := e.(type) { - case error: - return e - default: - return fmt.Errorf("recovered from unexpected panic: %T %s", e, e) - } -} diff --git a/p2p/transport/websocket/conn_native.go b/p2p/transport/websocket/conn_native.go deleted file mode 100644 index 37ff31edb4..0000000000 --- a/p2p/transport/websocket/conn_native.go +++ /dev/null @@ -1,146 +0,0 @@ -//go:build !js -// +build !js - -package websocket - -import ( - "io" - "net" - "sync" - "time" - - ws "github.com/gorilla/websocket" -) - -// Conn implements net.Conn interface for gorilla/websocket. -type Conn struct { - *ws.Conn - DefaultMessageType int - reader io.Reader - closeOnce sync.Once - - readLock, writeLock sync.Mutex -} - -func (c *Conn) Read(b []byte) (int, error) { - c.readLock.Lock() - defer c.readLock.Unlock() - - if c.reader == nil { - if err := c.prepNextReader(); err != nil { - return 0, err - } - } - - for { - n, err := c.reader.Read(b) - switch err { - case io.EOF: - c.reader = nil - - if n > 0 { - return n, nil - } - - if err := c.prepNextReader(); err != nil { - return 0, err - } - - // explicitly looping - default: - return n, err - } - } -} - -func (c *Conn) prepNextReader() error { - t, r, err := c.Conn.NextReader() - if err != nil { - if wserr, ok := err.(*ws.CloseError); ok { - if wserr.Code == 1000 || wserr.Code == 1005 { - return io.EOF - } - } - return err - } - - if t == ws.CloseMessage { - return io.EOF - } - - c.reader = r - return nil -} - -func (c *Conn) Write(b []byte) (n int, err error) { - c.writeLock.Lock() - defer c.writeLock.Unlock() - - if err := c.Conn.WriteMessage(c.DefaultMessageType, b); err != nil { - return 0, err - } - - return len(b), nil -} - -// Close closes the connection. Only the first call to Close will receive the -// close error, subsequent and concurrent calls will return nil. -// This method is thread-safe. -func (c *Conn) Close() error { - var err error - c.closeOnce.Do(func() { - err1 := c.Conn.WriteControl( - ws.CloseMessage, - ws.FormatCloseMessage(ws.CloseNormalClosure, "closed"), - time.Now().Add(GracefulCloseTimeout), - ) - err2 := c.Conn.Close() - switch { - case err1 != nil: - err = err1 - case err2 != nil: - err = err2 - } - }) - return err -} - -func (c *Conn) LocalAddr() net.Addr { - return NewAddr(c.Conn.LocalAddr().String()) -} - -func (c *Conn) RemoteAddr() net.Addr { - return NewAddr(c.Conn.RemoteAddr().String()) -} - -func (c *Conn) SetDeadline(t time.Time) error { - if err := c.SetReadDeadline(t); err != nil { - return err - } - - return c.SetWriteDeadline(t) -} - -func (c *Conn) SetReadDeadline(t time.Time) error { - // Don't lock when setting the read deadline. That would prevent us from - // interrupting an in-progress read. - return c.Conn.SetReadDeadline(t) -} - -func (c *Conn) SetWriteDeadline(t time.Time) error { - // Unlike the read deadline, we need to lock when setting the write - // deadline. - - c.writeLock.Lock() - defer c.writeLock.Unlock() - - return c.Conn.SetWriteDeadline(t) -} - -// NewConn creates a Conn given a regular gorilla/websocket Conn. -func NewConn(raw *ws.Conn) *Conn { - return &Conn{ - Conn: raw, - DefaultMessageType: ws.BinaryMessage, - } -} diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go index 8931b1f17c..a1f3fd465e 100644 --- a/p2p/transport/websocket/listener.go +++ b/p2p/transport/websocket/listener.go @@ -1,6 +1,3 @@ -//go:build !js -// +build !js - package websocket import ( diff --git a/p2p/transport/websocket/muxer_test.go b/p2p/transport/websocket/muxer_test.go deleted file mode 100644 index 8042c183db..0000000000 --- a/p2p/transport/websocket/muxer_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package websocket - -import ( - "testing" - - csms "github.com/libp2p/go-conn-security-multistream" - "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/sec" - "github.com/libp2p/go-libp2p-core/sec/insecure" - "github.com/libp2p/go-libp2p-core/test" -) - -func newSecureMuxer(t *testing.T, id peer.ID) sec.SecureMuxer { - t.Helper() - priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256) - if err != nil { - t.Fatal(err) - } - var secMuxer csms.SSMuxer - secMuxer.AddTransport(insecure.ID, insecure.NewWithIdentity(id, priv)) - return &secMuxer -} diff --git a/p2p/transport/websocket/tools/go.mod b/p2p/transport/websocket/tools/go.mod deleted file mode 100644 index d295a7a3bd..0000000000 --- a/p2p/transport/websocket/tools/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/libp2p/go-ws-transport/tools - -go 1.16 - -require github.com/agnivade/wasmbrowsertest v0.3.5 diff --git a/p2p/transport/websocket/tools/go.sum b/p2p/transport/websocket/tools/go.sum deleted file mode 100644 index 164a43e812..0000000000 --- a/p2p/transport/websocket/tools/go.sum +++ /dev/null @@ -1,31 +0,0 @@ -github.com/agnivade/wasmbrowsertest v0.3.5 h1:U8ICR7Xa3LBGQb57HtbbXf5KAMjQhiWrCWlr1kD77Cw= -github.com/agnivade/wasmbrowsertest v0.3.5/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI= -github.com/chromedp/cdproto v0.0.0-20190614062957-d6d2f92b486d/go.mod h1:S8mB5wY3vV+vRIzf39xDXsw3XKYewW9X6rW2aEmkrSw= -github.com/chromedp/cdproto v0.0.0-20190621002710-8cbd498dd7a0 h1:4Wocv9f+KWF4GtZudyrn8JSBTgHQbGp86mcsoH7j1iQ= -github.com/chromedp/cdproto v0.0.0-20190621002710-8cbd498dd7a0/go.mod h1:S8mB5wY3vV+vRIzf39xDXsw3XKYewW9X6rW2aEmkrSw= -github.com/chromedp/chromedp v0.3.1-0.20190619195644-fd957a4d2901 h1:tg66ykM8VYqP9k4DFQwSMnYv84HNTruF+GR6kefFNg4= -github.com/chromedp/chromedp v0.3.1-0.20190619195644-fd957a4d2901/go.mod h1:mJdvfrVn594N9tfiPecUidF6W5jPRKHymqHfzbobPsM= -github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/go-interpreter/wagon v0.5.1-0.20190713202023-55a163980b6c h1:DLLAPVFrk9iNzljMKF512CUmrFImQ6WU3sDiUS4IRqk= -github.com/go-interpreter/wagon v0.5.1-0.20190713202023-55a163980b6c/go.mod h1:5+b/MBYkclRZngKF5s6qrgWxSLgE9F5dFdO1hAueZLc= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f h1:Jnx61latede7zDD3DiiP4gmNz33uK0U5HDUaF0a/HVQ= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/knq/sysutil v0.0.0-20181215143952-f05b59f0f307 h1:vl4eIlySbjertFaNwiMjXsGrFVK25aOWLq7n+3gh2ls= -github.com/knq/sysutil v0.0.0-20181215143952-f05b59f0f307/go.mod h1:BjPj+aVjl9FW/cCGiF3nGh5v+9Gd3VCgBQbod/GlMaQ= -github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481 h1:IaSjLMT6WvkoZZjspGxy3rdaTEmWLoRm49WbtVUi9sA= -github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc h1:RTUQlKzoZZVG3umWNzOYeFecQLIh+dbxXvJp1zPQJTI= -github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc/go.mod h1:NoCfSFWosfqMqmmD7hApkirIK9ozpHjxRnRxs1l413A= -golang.org/x/sys v0.0.0-20190306220234-b354f8bf4d9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190618155005-516e3c20635f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= -golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/p2p/transport/websocket/tools/main.go b/p2p/transport/websocket/tools/main.go deleted file mode 100644 index f9eebf1c07..0000000000 --- a/p2p/transport/websocket/tools/main.go +++ /dev/null @@ -1,2 +0,0 @@ -// Makes this a valid go package, regardless of the build flags. -package tools diff --git a/p2p/transport/websocket/tools/tools.go b/p2p/transport/websocket/tools/tools.go deleted file mode 100644 index f4c550d7bd..0000000000 --- a/p2p/transport/websocket/tools/tools.go +++ /dev/null @@ -1,6 +0,0 @@ -//go:build tools -// +build tools - -package tools - -import _ "github.com/agnivade/wasmbrowsertest" diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index a620904a67..5f3d69594b 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -3,11 +3,15 @@ package websocket import ( "context" + "net" + "net/http" + "net/url" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/transport" + ws "github.com/gorilla/websocket" ma "github.com/multiformats/go-multiaddr" mafmt "github.com/multiformats/go-multiaddr-fmt" manet "github.com/multiformats/go-multiaddr/net" @@ -25,7 +29,13 @@ func init() { manet.RegisterToNetAddr(ConvertWebsocketMultiaddrToNetAddr, "ws") } -var _ transport.Transport = (*WebsocketTransport)(nil) +// Default gorilla upgrader +var upgrader = ws.Upgrader{ + // Allow requests from *all* origins. + CheckOrigin: func(r *http.Request) bool { + return true + }, +} // WebsocketTransport is the actual go-libp2p transport type WebsocketTransport struct { @@ -33,6 +43,8 @@ type WebsocketTransport struct { rcmgr network.ResourceManager } +var _ transport.Transport = (*WebsocketTransport)(nil) + func New(u transport.Upgrader, rcmgr network.ResourceManager) *WebsocketTransport { if rcmgr == nil { rcmgr = network.NullResourceManager @@ -67,3 +79,77 @@ func (t *WebsocketTransport) Dial(ctx context.Context, raddr ma.Multiaddr, p pee } return t.upgrader.Upgrade(ctx, t, macon, network.DirOutbound, p, connScope) } + +func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) { + wsurl, err := parseMultiaddr(raddr) + if err != nil { + return nil, err + } + + wscon, _, err := ws.DefaultDialer.Dial(wsurl, nil) + if err != nil { + return nil, err + } + + mnc, err := manet.WrapNetConn(NewConn(wscon)) + if err != nil { + wscon.Close() + return nil, err + } + return mnc, nil +} + +func (t *WebsocketTransport) maListen(a ma.Multiaddr) (manet.Listener, error) { + lnet, lnaddr, err := manet.DialArgs(a) + if err != nil { + return nil, err + } + + nl, err := net.Listen(lnet, lnaddr) + if err != nil { + return nil, err + } + + u, err := url.Parse("http://" + nl.Addr().String()) + if err != nil { + nl.Close() + return nil, err + } + + malist, err := t.wrapListener(nl, u) + if err != nil { + nl.Close() + return nil, err + } + + go malist.serve() + + return malist, nil +} + +func (t *WebsocketTransport) Listen(a ma.Multiaddr) (transport.Listener, error) { + malist, err := t.maListen(a) + if err != nil { + return nil, err + } + return t.upgrader.UpgradeListener(t, malist), nil +} + +func (t *WebsocketTransport) wrapListener(l net.Listener, origin *url.URL) (*listener, error) { + laddr, err := manet.FromNetAddr(l.Addr()) + if err != nil { + return nil, err + } + wsma, err := ma.NewMultiaddr("/ws") + if err != nil { + return nil, err + } + laddr = laddr.Encapsulate(wsma) + + return &listener{ + laddr: laddr, + Listener: l, + incoming: make(chan *Conn), + closed: make(chan struct{}), + }, nil +} diff --git a/p2p/transport/websocket/websocket_browser.go b/p2p/transport/websocket/websocket_browser.go deleted file mode 100644 index eef1f981da..0000000000 --- a/p2p/transport/websocket/websocket_browser.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build js && wasm -// +build js,wasm - -package websocket - -import ( - "context" - "errors" - "syscall/js" - - "github.com/libp2p/go-libp2p-core/transport" - ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr/net" -) - -func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) { - wsurl, err := parseMultiaddr(raddr) - if err != nil { - return nil, err - } - - rawConn := js.Global().Get("WebSocket").New(wsurl) - conn := NewConn(rawConn) - if err := conn.waitForOpen(); err != nil { - conn.Close() - return nil, err - } - mnc, err := manet.WrapNetConn(conn) - if err != nil { - conn.Close() - return nil, err - } - - return mnc, nil -} - -func (t *WebsocketTransport) Listen(a ma.Multiaddr) (transport.Listener, error) { - return nil, errors.New("Listen not implemented on js/wasm") -} diff --git a/p2p/transport/websocket/websocket_native.go b/p2p/transport/websocket/websocket_native.go deleted file mode 100644 index b652045e89..0000000000 --- a/p2p/transport/websocket/websocket_native.go +++ /dev/null @@ -1,98 +0,0 @@ -//go:build !js -// +build !js - -package websocket - -import ( - "context" - "net" - "net/http" - "net/url" - - ws "github.com/gorilla/websocket" - "github.com/libp2p/go-libp2p-core/transport" - ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr/net" -) - -// Default gorilla upgrader -var upgrader = ws.Upgrader{ - // Allow requests from *all* origins. - CheckOrigin: func(r *http.Request) bool { - return true - }, -} - -func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (manet.Conn, error) { - wsurl, err := parseMultiaddr(raddr) - if err != nil { - return nil, err - } - - wscon, _, err := ws.DefaultDialer.Dial(wsurl, nil) - if err != nil { - return nil, err - } - - mnc, err := manet.WrapNetConn(NewConn(wscon)) - if err != nil { - wscon.Close() - return nil, err - } - return mnc, nil -} - -func (t *WebsocketTransport) maListen(a ma.Multiaddr) (manet.Listener, error) { - lnet, lnaddr, err := manet.DialArgs(a) - if err != nil { - return nil, err - } - - nl, err := net.Listen(lnet, lnaddr) - if err != nil { - return nil, err - } - - u, err := url.Parse("http://" + nl.Addr().String()) - if err != nil { - nl.Close() - return nil, err - } - - malist, err := t.wrapListener(nl, u) - if err != nil { - nl.Close() - return nil, err - } - - go malist.serve() - - return malist, nil -} - -func (t *WebsocketTransport) Listen(a ma.Multiaddr) (transport.Listener, error) { - malist, err := t.maListen(a) - if err != nil { - return nil, err - } - return t.upgrader.UpgradeListener(t, malist), nil -} - -func (t *WebsocketTransport) wrapListener(l net.Listener, origin *url.URL) (*listener, error) { - laddr, err := manet.FromNetAddr(l.Addr()) - if err != nil { - return nil, err - } - wsma, err := ma.NewMultiaddr("/ws") - if err != nil { - return nil, err - } - laddr = laddr.Encapsulate(wsma) - - return &listener{ - laddr: laddr, - Listener: l, - incoming: make(chan *Conn), - closed: make(chan struct{}), - }, nil -} diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index a9ac9183c6..e83e3a859b 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -1,6 +1,3 @@ -//go:build !js -// +build !js - package websocket import ( @@ -11,12 +8,31 @@ import ( "testing" "testing/iotest" + csms "github.com/libp2p/go-conn-security-multistream" + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/sec" + "github.com/libp2p/go-libp2p-core/sec/insecure" + "github.com/libp2p/go-libp2p-core/test" + mplex "github.com/libp2p/go-libp2p-mplex" ttransport "github.com/libp2p/go-libp2p-testing/suites/transport" tptu "github.com/libp2p/go-libp2p-transport-upgrader" ma "github.com/multiformats/go-multiaddr" ) +//lint:ignore U1000 // see https://github.com/dominikh/go-tools/issues/633 +func newSecureMuxer(t *testing.T, id peer.ID) sec.SecureMuxer { + t.Helper() + priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256) + if err != nil { + t.Fatal(err) + } + var secMuxer csms.SSMuxer + secMuxer.AddTransport(insecure.ID, insecure.NewWithIdentity(id, priv)) + return &secMuxer +} + func TestCanDial(t *testing.T) { addrWs, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") if err != nil { From a55402dd29206b98f53f61efb9f002c26dbb17e7 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Thu, 23 Jan 2020 15:32:32 -0800 Subject: [PATCH 86/93] add support for wss dialing --- p2p/transport/websocket/addrs.go | 98 ++++++++++++++---- p2p/transport/websocket/addrs_test.go | 8 +- p2p/transport/websocket/conn.go | 22 ++-- p2p/transport/websocket/listener.go | 3 +- p2p/transport/websocket/websocket.go | 36 +++++-- p2p/transport/websocket/websocket_test.go | 118 ++++++++++++++-------- 6 files changed, 203 insertions(+), 82 deletions(-) diff --git a/p2p/transport/websocket/addrs.go b/p2p/transport/websocket/addrs.go index e789399da3..608eb2d0da 100644 --- a/p2p/transport/websocket/addrs.go +++ b/p2p/transport/websocket/addrs.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "net/url" + "strconv" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr/net" @@ -21,22 +22,36 @@ func (addr *Addr) Network() string { return "websocket" } -// NewAddr creates a new Addr using the given host string +// NewAddr creates an Addr with `ws` scheme (insecure). +// +// Deprecated. Use NewAddrWithScheme. func NewAddr(host string) *Addr { + // Older versions of the transport only supported insecure connections (i.e. + // WS instead of WSS). Assume that is the case here. + return NewAddrWithScheme(host, false) +} + +// NewAddrWithScheme creates a new Addr using the given host string. isSecure +// should be true for WSS connections and false for WS. +func NewAddrWithScheme(host string, isSecure bool) *Addr { + scheme := "ws" + if isSecure { + scheme = "wss" + } return &Addr{ URL: &url.URL{ - Host: host, + Scheme: scheme, + Host: host, }, } } func ConvertWebsocketMultiaddrToNetAddr(maddr ma.Multiaddr) (net.Addr, error) { - _, host, err := manet.DialArgs(maddr) + url, err := parseMultiaddr(maddr) if err != nil { return nil, err } - - return NewAddr(host), nil + return &Addr{URL: url}, nil } func ParseWebsocketNetAddr(a net.Addr) (ma.Multiaddr, error) { @@ -45,17 +60,43 @@ func ParseWebsocketNetAddr(a net.Addr) (ma.Multiaddr, error) { return nil, fmt.Errorf("not a websocket address") } - tcpaddr, err := net.ResolveTCPAddr("tcp", wsa.Host) - if err != nil { - return nil, err + var ( + tcpma ma.Multiaddr + err error + port int + host = wsa.Hostname() + ) + + // Get the port + if portStr := wsa.Port(); portStr != "" { + port, err = strconv.Atoi(portStr) + if err != nil { + return nil, fmt.Errorf("failed to parse port '%q': %s", portStr, err) + } + } else { + return nil, fmt.Errorf("invalid port in url: '%q'", wsa.URL) } - tcpma, err := manet.FromNetAddr(tcpaddr) - if err != nil { - return nil, err + // NOTE: Ignoring IPv6 zones... + // Detect if host is IP address or DNS + if ip := net.ParseIP(host); ip != nil { + // Assume IP address + tcpma, err = manet.FromNetAddr(&net.TCPAddr{ + IP: ip, + Port: port, + }) + if err != nil { + return nil, err + } + } else { + // Assume DNS name + tcpma, err = ma.NewMultiaddr(fmt.Sprintf("/dns/%s/tcp/%d", host, port)) + if err != nil { + return nil, err + } } - wsma, err := ma.NewMultiaddr("/ws") + wsma, err := ma.NewMultiaddr("/" + wsa.Scheme) if err != nil { return nil, err } @@ -63,11 +104,34 @@ func ParseWebsocketNetAddr(a net.Addr) (ma.Multiaddr, error) { return tcpma.Encapsulate(wsma), nil } -func parseMultiaddr(a ma.Multiaddr) (string, error) { - _, host, err := manet.DialArgs(a) - if err != nil { - return "", err +func parseMultiaddr(maddr ma.Multiaddr) (*url.URL, error) { + // Only look at the _last_ component. + maddr, wscomponent := ma.SplitLast(maddr) + if maddr == nil || wscomponent == nil { + return nil, fmt.Errorf("websocket addrs need at least two components") + } + + var scheme string + switch wscomponent.Protocol().Code { + case ma.P_WS: + scheme = "ws" + case ma.P_WSS: + scheme = "wss" + default: + return nil, fmt.Errorf("not a websocket multiaddr") } - return "ws://" + host, nil + network, host, err := manet.DialArgs(maddr) + if err != nil { + return nil, err + } + switch network { + case "tcp", "tcp4", "tcp6": + default: + return nil, fmt.Errorf("unsupported websocket network %s", network) + } + return &url.URL{ + Scheme: scheme, + Host: host, + }, nil } diff --git a/p2p/transport/websocket/addrs_test.go b/p2p/transport/websocket/addrs_test.go index d962760088..1a73c28762 100644 --- a/p2p/transport/websocket/addrs_test.go +++ b/p2p/transport/websocket/addrs_test.go @@ -17,7 +17,7 @@ func TestMultiaddrParsing(t *testing.T) { if err != nil { t.Fatal(err) } - if wsaddr != "ws://127.0.0.1:5555" { + if wsaddr.String() != "ws://127.0.0.1:5555" { t.Fatalf("expected ws://127.0.0.1:5555, got %s", wsaddr) } } @@ -37,7 +37,7 @@ func TestParseWebsocketNetAddr(t *testing.T) { t.Fatalf("expect \"not a websocket address\", got \"%s\"", err) } - wsAddr := NewAddr("127.0.0.1:5555") + wsAddr := NewAddrWithScheme("127.0.0.1:5555", false) parsed, err := ParseWebsocketNetAddr(wsAddr) if err != nil { t.Fatal(err) @@ -58,8 +58,8 @@ func TestConvertWebsocketMultiaddrToNetAddr(t *testing.T) { if err != nil { t.Fatal(err) } - if wsaddr.String() != "//127.0.0.1:5555" { - t.Fatalf("expected //127.0.0.1:5555, got %s", wsaddr) + if wsaddr.String() != "ws://127.0.0.1:5555" { + t.Fatalf("expected ws://127.0.0.1:5555, got %s", wsaddr) } if wsaddr.Network() != "websocket" { t.Fatalf("expected network: \"websocket\", got \"%s\"", wsaddr.Network()) diff --git a/p2p/transport/websocket/conn.go b/p2p/transport/websocket/conn.go index 5f520a1e76..6f2e0a7667 100644 --- a/p2p/transport/websocket/conn.go +++ b/p2p/transport/websocket/conn.go @@ -16,6 +16,7 @@ var GracefulCloseTimeout = 100 * time.Millisecond // Conn implements net.Conn interface for gorilla/websocket. type Conn struct { *ws.Conn + secure bool DefaultMessageType int reader io.Reader closeOnce sync.Once @@ -25,6 +26,15 @@ type Conn struct { var _ net.Conn = (*Conn)(nil) +// NewConn creates a Conn given a regular gorilla/websocket Conn. +func NewConn(raw *ws.Conn, secure bool) *Conn { + return &Conn{ + Conn: raw, + secure: secure, + DefaultMessageType: ws.BinaryMessage, + } +} + func (c *Conn) Read(b []byte) (int, error) { c.readLock.Lock() defer c.readLock.Unlock() @@ -109,11 +119,11 @@ func (c *Conn) Close() error { } func (c *Conn) LocalAddr() net.Addr { - return NewAddr(c.Conn.LocalAddr().String()) + return NewAddrWithScheme(c.Conn.LocalAddr().String(), c.secure) } func (c *Conn) RemoteAddr() net.Addr { - return NewAddr(c.Conn.RemoteAddr().String()) + return NewAddrWithScheme(c.Conn.RemoteAddr().String(), c.secure) } func (c *Conn) SetDeadline(t time.Time) error { @@ -139,11 +149,3 @@ func (c *Conn) SetWriteDeadline(t time.Time) error { return c.Conn.SetWriteDeadline(t) } - -// NewConn creates a Conn given a regular gorilla/websocket Conn. -func NewConn(raw *ws.Conn) *Conn { - return &Conn{ - Conn: raw, - DefaultMessageType: ws.BinaryMessage, - } -} diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go index a1f3fd465e..d2cf5403de 100644 --- a/p2p/transport/websocket/listener.go +++ b/p2p/transport/websocket/listener.go @@ -11,7 +11,6 @@ import ( type listener struct { net.Listener - laddr ma.Multiaddr closed chan struct{} @@ -31,7 +30,7 @@ func (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { } select { - case l.incoming <- NewConn(c): + case l.incoming <- NewConn(c, false): case <-l.closed: c.Close() } diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 5f3d69594b..77d1f65905 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -3,6 +3,7 @@ package websocket import ( "context" + "crypto/tls" "net" "net/http" "net/url" @@ -22,11 +23,12 @@ var WsFmt = mafmt.And(mafmt.TCP, mafmt.Base(ma.P_WS)) // This is _not_ WsFmt because we want the transport to stick to dialing fully // resolved addresses. -var dialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_TCP), mafmt.Base(ma.P_WS)) +var dialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_TCP), mafmt.Or(mafmt.Base(ma.P_WS), mafmt.Base(ma.P_WSS))) func init() { manet.RegisterFromNetAddr(ParseWebsocketNetAddr, "websocket") manet.RegisterToNetAddr(ConvertWebsocketMultiaddrToNetAddr, "ws") + manet.RegisterToNetAddr(ConvertWebsocketMultiaddrToNetAddr, "wss") } // Default gorilla upgrader @@ -37,22 +39,44 @@ var upgrader = ws.Upgrader{ }, } +type Option func(*WebsocketTransport) error + +// WithTLSClientConfig sets a TLS client configuration on the WebSocket Dialer. Only +// relevant for non-browser usages. +// +// Some useful use cases include setting InsecureSkipVerify to `true`, or +// setting user-defined trusted CA certificates. +func WithTLSClientConfig(c *tls.Config) Option { + return func(t *WebsocketTransport) error { + t.tlsClientConf = c + return nil + } +} + // WebsocketTransport is the actual go-libp2p transport type WebsocketTransport struct { upgrader transport.Upgrader rcmgr network.ResourceManager + + tlsClientConf *tls.Config } var _ transport.Transport = (*WebsocketTransport)(nil) -func New(u transport.Upgrader, rcmgr network.ResourceManager) *WebsocketTransport { +func New(u transport.Upgrader, rcmgr network.ResourceManager, opts ...Option) (*WebsocketTransport, error) { if rcmgr == nil { rcmgr = network.NullResourceManager } - return &WebsocketTransport{ + t := &WebsocketTransport{ upgrader: u, rcmgr: rcmgr, } + for _, opt := range opts { + if err := opt(t); err != nil { + return nil, err + } + } + return t, nil } func (t *WebsocketTransport) CanDial(a ma.Multiaddr) bool { @@ -60,7 +84,7 @@ func (t *WebsocketTransport) CanDial(a ma.Multiaddr) bool { } func (t *WebsocketTransport) Protocols() []int { - return []int{ma.ProtocolWithCode(ma.P_WS).Code} + return []int{ma.P_WS, ma.P_WSS} } func (t *WebsocketTransport) Proxy() bool { @@ -86,12 +110,12 @@ func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (ma return nil, err } - wscon, _, err := ws.DefaultDialer.Dial(wsurl, nil) + wscon, _, err := ws.DefaultDialer.Dial(wsurl.String(), nil) if err != nil { return nil, err } - mnc, err := manet.WrapNetConn(NewConn(wscon)) + mnc, err := manet.WrapNetConn(NewConn(wscon, wsurl.Scheme == "wss")) if err != nil { wscon.Close() return nil, err diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index e83e3a859b..addd56d737 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -3,85 +3,115 @@ package websocket import ( "bytes" "context" + "crypto/tls" "io" "io/ioutil" + "net" "testing" - "testing/iotest" - csms "github.com/libp2p/go-conn-security-multistream" "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/sec" "github.com/libp2p/go-libp2p-core/sec/insecure" "github.com/libp2p/go-libp2p-core/test" + "github.com/libp2p/go-libp2p-core/transport" + csms "github.com/libp2p/go-conn-security-multistream" mplex "github.com/libp2p/go-libp2p-mplex" ttransport "github.com/libp2p/go-libp2p-testing/suites/transport" tptu "github.com/libp2p/go-libp2p-transport-upgrader" ma "github.com/multiformats/go-multiaddr" ) -//lint:ignore U1000 // see https://github.com/dominikh/go-tools/issues/633 -func newSecureMuxer(t *testing.T, id peer.ID) sec.SecureMuxer { +func newUpgrader(t *testing.T) (peer.ID, transport.Upgrader) { + t.Helper() + id, m := newSecureMuxer(t) + u, err := tptu.New(m, new(mplex.Transport)) + if err != nil { + t.Fatal(err) + } + return id, u +} + +func newSecureMuxer(t *testing.T) (peer.ID, sec.SecureMuxer) { t.Helper() priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256) if err != nil { t.Fatal(err) } + id, err := peer.IDFromPrivateKey(priv) + if err != nil { + t.Fatal(err) + } var secMuxer csms.SSMuxer secMuxer.AddTransport(insecure.ID, insecure.NewWithIdentity(id, priv)) - return &secMuxer + return id, &secMuxer } func TestCanDial(t *testing.T) { - addrWs, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555/ws") + d := &WebsocketTransport{} + if !d.CanDial(ma.StringCast("/ip4/127.0.0.1/tcp/5555/ws")) { + t.Fatal("expected to match websocket maddr, but did not") + } + if !d.CanDial(ma.StringCast("/ip4/127.0.0.1/tcp/5555/wss")) { + t.Fatal("expected to match secure websocket maddr, but did not") + } + if d.CanDial(ma.StringCast("/ip4/127.0.0.1/tcp/5555")) { + t.Fatal("expected to not match tcp maddr, but did") + } +} + +func TestDialWss(t *testing.T) { + if _, err := net.LookupIP("nyc-1.bootstrap.libp2p.io"); err != nil { + t.Skip("this test requries an internet connection and it seems like we currently don't have one") + } + raddr := ma.StringCast("/dns4/nyc-1.bootstrap.libp2p.io/tcp/443/wss") + rid, err := peer.Decode("QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm") if err != nil { t.Fatal(err) } - addrTCP, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/5555") + tlsConfig := &tls.Config{InsecureSkipVerify: true} + _, u := newUpgrader(t) + tpt, err := New(u, network.NullResourceManager, WithTLSClientConfig(tlsConfig)) if err != nil { t.Fatal(err) } - - d := &WebsocketTransport{} - matchTrue := d.CanDial(addrWs) - matchFalse := d.CanDial(addrTCP) - - if !matchTrue { - t.Fatal("expected to match websocket maddr, but did not") + conn, err := tpt.Dial(context.Background(), raddr, rid) + if err != nil { + t.Fatal(err) } - - if matchFalse { - t.Fatal("expected to not match tcp maddr, but did") + stream, err := conn.OpenStream(context.Background()) + if err != nil { + t.Fatal(err) } + defer stream.Close() } func TestWebsocketTransport(t *testing.T) { t.Skip("This test is failing, see https://github.com/libp2p/go-ws-transport/issues/99") - ua, err := tptu.New(newSecureMuxer(t, "peerA"), new(mplex.Transport)) + _, ua := newUpgrader(t) + ta, err := New(ua, nil) if err != nil { t.Fatal(err) } - ta := New(ua, nil) - ub, err := tptu.New(newSecureMuxer(t, "peerB"), new(mplex.Transport)) + _, ub := newUpgrader(t) + tb, err := New(ub, nil) if err != nil { t.Fatal(err) } - tb := New(ub, nil) - zero := "/ip4/127.0.0.1/tcp/0/ws" - ttransport.SubtestTransport(t, ta, tb, zero, "peerA") + ttransport.SubtestTransport(t, ta, tb, "/ip4/127.0.0.1/tcp/0/ws", "peerA") } func TestWebsocketListen(t *testing.T) { - zero, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0/ws") + id, u := newUpgrader(t) + tpt, err := New(u, network.NullResourceManager) if err != nil { t.Fatal(err) } - - tpt := &WebsocketTransport{} - l, err := tpt.maListen(zero) + l, err := tpt.Listen(ma.StringCast("/ip4/127.0.0.1/tcp/0/ws")) if err != nil { t.Fatal(err) } @@ -90,19 +120,20 @@ func TestWebsocketListen(t *testing.T) { msg := []byte("HELLO WORLD") go func() { - c, err := tpt.maDial(context.Background(), l.Multiaddr()) + c, err := tpt.Dial(context.Background(), l.Multiaddr(), id) if err != nil { t.Error(err) return } - - _, err = c.Write(msg) + str, err := c.OpenStream(context.Background()) if err != nil { t.Error(err) } - err = c.Close() - if err != nil { + defer str.Close() + + if _, err = str.Write(msg); err != nil { t.Error(err) + return } }() @@ -111,10 +142,13 @@ func TestWebsocketListen(t *testing.T) { t.Fatal(err) } defer c.Close() + str, err := c.AcceptStream() + if err != nil { + t.Fatal(err) + } + defer str.Close() - obr := iotest.OneByteReader(c) - - out, err := ioutil.ReadAll(obr) + out, err := ioutil.ReadAll(str) if err != nil { t.Fatal(err) } @@ -125,13 +159,12 @@ func TestWebsocketListen(t *testing.T) { } func TestConcurrentClose(t *testing.T) { - zero, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0/ws") + _, u := newUpgrader(t) + tpt, err := New(u, network.NullResourceManager) if err != nil { t.Fatal(err) } - - tpt := &WebsocketTransport{} - l, err := tpt.maListen(zero) + l, err := tpt.maListen(ma.StringCast("/ip4/127.0.0.1/tcp/0/ws")) if err != nil { t.Fatal(err) } @@ -166,13 +199,12 @@ func TestConcurrentClose(t *testing.T) { } func TestWriteZero(t *testing.T) { - zero, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0/ws") + _, u := newUpgrader(t) + tpt, err := New(u, network.NullResourceManager) if err != nil { t.Fatal(err) } - - tpt := &WebsocketTransport{} - l, err := tpt.maListen(zero) + l, err := tpt.maListen(ma.StringCast("/ip4/127.0.0.1/tcp/0/ws")) if err != nil { t.Fatal(err) } From 571798514eb9c4ab0c82120be35cade732e85c92 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 13 Feb 2022 15:58:13 +0400 Subject: [PATCH 87/93] remove unused origin function parameteter in wrapListener --- p2p/transport/websocket/websocket.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 77d1f65905..a416aa97cf 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -6,7 +6,6 @@ import ( "crypto/tls" "net" "net/http" - "net/url" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" @@ -128,24 +127,15 @@ func (t *WebsocketTransport) maListen(a ma.Multiaddr) (manet.Listener, error) { if err != nil { return nil, err } - nl, err := net.Listen(lnet, lnaddr) if err != nil { return nil, err } - - u, err := url.Parse("http://" + nl.Addr().String()) - if err != nil { - nl.Close() - return nil, err - } - - malist, err := t.wrapListener(nl, u) + malist, err := t.wrapListener(nl) if err != nil { nl.Close() return nil, err } - go malist.serve() return malist, nil @@ -159,7 +149,7 @@ func (t *WebsocketTransport) Listen(a ma.Multiaddr) (transport.Listener, error) return t.upgrader.UpgradeListener(t, malist), nil } -func (t *WebsocketTransport) wrapListener(l net.Listener, origin *url.URL) (*listener, error) { +func (t *WebsocketTransport) wrapListener(l net.Listener) (*listener, error) { laddr, err := manet.FromNetAddr(l.Addr()) if err != nil { return nil, err From c6eb1b47c14d2b96f0d8802b8596d242bd7c6645 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 13 Feb 2022 16:01:06 +0400 Subject: [PATCH 88/93] only initialize the ws multiaddr component once --- p2p/transport/websocket/websocket.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index a416aa97cf..e09cf3f593 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -20,6 +20,8 @@ import ( // WsFmt is multiaddr formatter for WsProtocol var WsFmt = mafmt.And(mafmt.TCP, mafmt.Base(ma.P_WS)) +var wsma = ma.StringCast("/ws") + // This is _not_ WsFmt because we want the transport to stick to dialing fully // resolved addresses. var dialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_TCP), mafmt.Or(mafmt.Base(ma.P_WS), mafmt.Base(ma.P_WSS))) @@ -154,14 +156,8 @@ func (t *WebsocketTransport) wrapListener(l net.Listener) (*listener, error) { if err != nil { return nil, err } - wsma, err := ma.NewMultiaddr("/ws") - if err != nil { - return nil, err - } - laddr = laddr.Encapsulate(wsma) - return &listener{ - laddr: laddr, + laddr: laddr.Encapsulate(wsma), Listener: l, incoming: make(chan *Conn), closed: make(chan struct{}), From 3384493ce850c1fac796029195072ec8b2fcbc0a Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 13 Feb 2022 16:11:23 +0400 Subject: [PATCH 89/93] introduce a constructor for the listener --- p2p/transport/websocket/listener.go | 13 +++++++++++++ p2p/transport/websocket/websocket.go | 20 +++----------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go index d2cf5403de..9247aa85a8 100644 --- a/p2p/transport/websocket/listener.go +++ b/p2p/transport/websocket/listener.go @@ -17,6 +17,19 @@ type listener struct { incoming chan *Conn } +func newListener(l net.Listener) (*listener, error) { + laddr, err := manet.FromNetAddr(l.Addr()) + if err != nil { + return nil, err + } + return &listener{ + Listener: l, + laddr: laddr.Encapsulate(wsma), + incoming: make(chan *Conn), + closed: make(chan struct{}), + }, nil +} + func (l *listener) serve() { defer close(l.closed) _ = http.Serve(l.Listener, l) diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index e09cf3f593..1f8754647c 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -133,14 +133,13 @@ func (t *WebsocketTransport) maListen(a ma.Multiaddr) (manet.Listener, error) { if err != nil { return nil, err } - malist, err := t.wrapListener(nl) + l, err := newListener(nl) if err != nil { nl.Close() return nil, err } - go malist.serve() - - return malist, nil + go l.serve() + return l, nil } func (t *WebsocketTransport) Listen(a ma.Multiaddr) (transport.Listener, error) { @@ -150,16 +149,3 @@ func (t *WebsocketTransport) Listen(a ma.Multiaddr) (transport.Listener, error) } return t.upgrader.UpgradeListener(t, malist), nil } - -func (t *WebsocketTransport) wrapListener(l net.Listener) (*listener, error) { - laddr, err := manet.FromNetAddr(l.Addr()) - if err != nil { - return nil, err - } - return &listener{ - laddr: laddr.Encapsulate(wsma), - Listener: l, - incoming: make(chan *Conn), - closed: make(chan struct{}), - }, nil -} From c3eae931c3ac5e9930a9e4e6f7390a2db6d5805b Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 13 Feb 2022 16:13:36 +0400 Subject: [PATCH 90/93] wait for the http server to shutdown before returning from Close --- p2p/transport/websocket/listener.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go index 9247aa85a8..985d5c74ff 100644 --- a/p2p/transport/websocket/listener.go +++ b/p2p/transport/websocket/listener.go @@ -10,7 +10,8 @@ import ( ) type listener struct { - net.Listener + nl net.Listener + laddr ma.Multiaddr closed chan struct{} @@ -23,7 +24,7 @@ func newListener(l net.Listener) (*listener, error) { return nil, err } return &listener{ - Listener: l, + nl: l, laddr: laddr.Encapsulate(wsma), incoming: make(chan *Conn), closed: make(chan struct{}), @@ -32,7 +33,7 @@ func newListener(l net.Listener) (*listener, error) { func (l *listener) serve() { defer close(l.closed) - _ = http.Serve(l.Listener, l) + _ = http.Serve(l.nl, l) } func (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -69,6 +70,16 @@ func (l *listener) Accept() (manet.Conn, error) { } } +func (l *listener) Addr() net.Addr { + return l.nl.Addr() +} + +func (l *listener) Close() error { + err := l.nl.Close() + <-l.closed + return err +} + func (l *listener) Multiaddr() ma.Multiaddr { return l.laddr } From 292e430e58fea0de44afb5af62173c93122ab6aa Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 13 Feb 2022 16:27:15 +0400 Subject: [PATCH 91/93] add an option to use encrypted websockets for listening --- p2p/transport/websocket/listener.go | 51 ++++++- p2p/transport/websocket/websocket.go | 36 +++-- p2p/transport/websocket/websocket_test.go | 174 +++++++++++++++++----- 3 files changed, 201 insertions(+), 60 deletions(-) diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go index 985d5c74ff..24c79f9d17 100644 --- a/p2p/transport/websocket/listener.go +++ b/p2p/transport/websocket/listener.go @@ -1,6 +1,7 @@ package websocket import ( + "crypto/tls" "fmt" "net" "net/http" @@ -9,8 +10,14 @@ import ( manet "github.com/multiformats/go-multiaddr/net" ) +var ( + wsma = ma.StringCast("/ws") + wssma = ma.StringCast("/wss") +) + type listener struct { - nl net.Listener + nl net.Listener + server http.Server laddr ma.Multiaddr @@ -18,22 +25,49 @@ type listener struct { incoming chan *Conn } -func newListener(l net.Listener) (*listener, error) { - laddr, err := manet.FromNetAddr(l.Addr()) +// newListener creates a new listener from a raw net.Listener. +// tlsConf may be nil (for unencrypted websockets). +func newListener(a ma.Multiaddr, tlsConf *tls.Config) (*listener, error) { + // Only look at the _last_ component. + maddr, wscomponent := ma.SplitLast(a) + isWSS := wscomponent.Equal(wssma) + if isWSS && tlsConf == nil { + return nil, fmt.Errorf("cannot listen on wss address %s without a tls.Config", a) + } + lnet, lnaddr, err := manet.DialArgs(maddr) + if err != nil { + return nil, err + } + nl, err := net.Listen(lnet, lnaddr) + if err != nil { + return nil, err + } + + laddr, err := manet.FromNetAddr(nl.Addr()) if err != nil { return nil, err } - return &listener{ - nl: l, - laddr: laddr.Encapsulate(wsma), + + ln := &listener{ + nl: nl, + laddr: laddr.Encapsulate(wscomponent), incoming: make(chan *Conn), closed: make(chan struct{}), - }, nil + } + ln.server = http.Server{Handler: ln} + if isWSS { + ln.server.TLSConfig = tlsConf + } + return ln, nil } func (l *listener) serve() { defer close(l.closed) - _ = http.Serve(l.nl, l) + if l.server.TLSConfig == nil { + l.server.Serve(l.nl) + } else { + l.server.ServeTLS(l.nl, "", "") + } } func (l *listener) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -75,6 +109,7 @@ func (l *listener) Addr() net.Addr { } func (l *listener) Close() error { + l.server.Close() err := l.nl.Close() <-l.closed return err diff --git a/p2p/transport/websocket/websocket.go b/p2p/transport/websocket/websocket.go index 1f8754647c..a65fcdccf1 100644 --- a/p2p/transport/websocket/websocket.go +++ b/p2p/transport/websocket/websocket.go @@ -4,24 +4,23 @@ package websocket import ( "context" "crypto/tls" - "net" "net/http" + "time" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/transport" - ws "github.com/gorilla/websocket" ma "github.com/multiformats/go-multiaddr" mafmt "github.com/multiformats/go-multiaddr-fmt" manet "github.com/multiformats/go-multiaddr/net" + + ws "github.com/gorilla/websocket" ) // WsFmt is multiaddr formatter for WsProtocol var WsFmt = mafmt.And(mafmt.TCP, mafmt.Base(ma.P_WS)) -var wsma = ma.StringCast("/ws") - // This is _not_ WsFmt because we want the transport to stick to dialing fully // resolved addresses. var dialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_TCP), mafmt.Or(mafmt.Base(ma.P_WS), mafmt.Base(ma.P_WSS))) @@ -54,12 +53,21 @@ func WithTLSClientConfig(c *tls.Config) Option { } } +// WithTLSConfig sets a TLS configuration for the WebSocket listener. +func WithTLSConfig(conf *tls.Config) Option { + return func(t *WebsocketTransport) error { + t.tlsConf = conf + return nil + } +} + // WebsocketTransport is the actual go-libp2p transport type WebsocketTransport struct { upgrader transport.Upgrader rcmgr network.ResourceManager tlsClientConf *tls.Config + tlsConf *tls.Config } var _ transport.Transport = (*WebsocketTransport)(nil) @@ -110,13 +118,18 @@ func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (ma if err != nil { return nil, err } + isWss := wsurl.Scheme == "wss" + dialer := ws.Dialer{HandshakeTimeout: 30 * time.Second} + if isWss { + dialer.TLSClientConfig = t.tlsClientConf - wscon, _, err := ws.DefaultDialer.Dial(wsurl.String(), nil) + } + wscon, _, err := dialer.DialContext(ctx, wsurl.String(), nil) if err != nil { return nil, err } - mnc, err := manet.WrapNetConn(NewConn(wscon, wsurl.Scheme == "wss")) + mnc, err := manet.WrapNetConn(NewConn(wscon, isWss)) if err != nil { wscon.Close() return nil, err @@ -125,17 +138,8 @@ func (t *WebsocketTransport) maDial(ctx context.Context, raddr ma.Multiaddr) (ma } func (t *WebsocketTransport) maListen(a ma.Multiaddr) (manet.Listener, error) { - lnet, lnaddr, err := manet.DialArgs(a) - if err != nil { - return nil, err - } - nl, err := net.Listen(lnet, lnaddr) - if err != nil { - return nil, err - } - l, err := newListener(nl) + l, err := newListener(a, t.tlsConf) if err != nil { - nl.Close() return nil, err } go l.serve() diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index addd56d737..43c895b081 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -1,13 +1,19 @@ package websocket import ( - "bytes" "context" + "crypto/rand" + "crypto/rsa" "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "fmt" "io" "io/ioutil" + "math/big" "net" "testing" + "time" "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/network" @@ -21,7 +27,9 @@ import ( mplex "github.com/libp2p/go-libp2p-mplex" ttransport "github.com/libp2p/go-libp2p-testing/suites/transport" tptu "github.com/libp2p/go-libp2p-transport-upgrader" + ma "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" ) func newUpgrader(t *testing.T) (peer.ID, transport.Upgrader) { @@ -49,6 +57,42 @@ func newSecureMuxer(t *testing.T) (peer.ID, sec.SecureMuxer) { return id, &secMuxer } +func lastComponent(t *testing.T, a ma.Multiaddr) ma.Multiaddr { + t.Helper() + _, wscomponent := ma.SplitLast(a) + require.NotNil(t, wscomponent) + if wscomponent.Equal(wsma) { + return wsma + } + if wscomponent.Equal(wssma) { + return wssma + } + t.Fatal("expected a ws or wss component") + return nil +} + +func generateTLSConfig(t *testing.T) *tls.Config { + t.Helper() + priv, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{}, + SignatureAlgorithm: x509.SHA256WithRSA, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), // valid for an hour + BasicConstraintsValid: true, + } + certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, priv.Public(), priv) + require.NoError(t, err) + return &tls.Config{ + Certificates: []tls.Certificate{{ + PrivateKey: priv, + Certificate: [][]byte{certDER}, + }}, + } +} + func TestCanDial(t *testing.T) { d := &WebsocketTransport{} if !d.CanDial(ma.StringCast("/ip4/127.0.0.1/tcp/5555/ws")) { @@ -105,65 +149,123 @@ func TestWebsocketTransport(t *testing.T) { ttransport.SubtestTransport(t, ta, tb, "/ip4/127.0.0.1/tcp/0/ws", "peerA") } -func TestWebsocketListen(t *testing.T) { - id, u := newUpgrader(t) - tpt, err := New(u, network.NullResourceManager) - if err != nil { - t.Fatal(err) +func connectAndExchangeData(t *testing.T, laddr ma.Multiaddr, secure bool) { + var opts []Option + var tlsConf *tls.Config + if secure { + tlsConf = generateTLSConfig(t) + opts = append(opts, WithTLSConfig(tlsConf)) } - l, err := tpt.Listen(ma.StringCast("/ip4/127.0.0.1/tcp/0/ws")) - if err != nil { - t.Fatal(err) + server, u := newUpgrader(t) + tpt, err := New(u, network.NullResourceManager, opts...) + require.NoError(t, err) + l, err := tpt.Listen(laddr) + require.NoError(t, err) + if secure { + require.Equal(t, lastComponent(t, l.Multiaddr()), wssma) + } else { + require.Equal(t, lastComponent(t, l.Multiaddr()), wsma) } defer l.Close() msg := []byte("HELLO WORLD") go func() { - c, err := tpt.Dial(context.Background(), l.Multiaddr(), id) - if err != nil { - t.Error(err) - return + var opts []Option + if secure { + opts = append(opts, WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true})) } + _, u := newUpgrader(t) + tpt, err := New(u, network.NullResourceManager, opts...) + require.NoError(t, err) + c, err := tpt.Dial(context.Background(), l.Multiaddr(), server) + require.NoError(t, err) str, err := c.OpenStream(context.Background()) - if err != nil { - t.Error(err) - } + require.NoError(t, err) defer str.Close() - - if _, err = str.Write(msg); err != nil { - t.Error(err) - return - } + _, err = str.Write(msg) + require.NoError(t, err) }() c, err := l.Accept() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer c.Close() str, err := c.AcceptStream() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer str.Close() out, err := ioutil.ReadAll(str) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + require.Equal(t, out, msg, "got wrong message") +} - if !bytes.Equal(out, msg) { - t.Fatal("got wrong message", out, msg) - } +func TestWebsocketConnection(t *testing.T) { + t.Run("unencrypted", func(t *testing.T) { + connectAndExchangeData(t, ma.StringCast("/ip4/127.0.0.1/tcp/0/ws"), false) + }) + t.Run("encrypted", func(t *testing.T) { + connectAndExchangeData(t, ma.StringCast("/ip4/127.0.0.1/tcp/0/wss"), true) + }) +} + +func TestWebsocketListenSecureFailWithoutTLSConfig(t *testing.T) { + _, u := newUpgrader(t) + tpt, err := New(u, network.NullResourceManager) + require.NoError(t, err) + addr := ma.StringCast("/ip4/127.0.0.1/tcp/0/wss") + _, err = tpt.Listen(addr) + require.EqualError(t, err, fmt.Sprintf("cannot listen on wss address %s without a tls.Config", addr)) +} + +func TestWebsocketListenSecureAndInsecure(t *testing.T) { + serverID, serverUpgrader := newUpgrader(t) + server, err := New(serverUpgrader, network.NullResourceManager, WithTLSConfig(generateTLSConfig(t))) + require.NoError(t, err) + + lnInsecure, err := server.Listen(ma.StringCast("/ip4/127.0.0.1/tcp/0/ws")) + require.NoError(t, err) + lnSecure, err := server.Listen(ma.StringCast("/ip4/127.0.0.1/tcp/0/wss")) + require.NoError(t, err) + + t.Run("insecure", func(t *testing.T) { + _, clientUpgrader := newUpgrader(t) + client, err := New(clientUpgrader, network.NullResourceManager, WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true})) + require.NoError(t, err) + + // dialing the insecure address should succeed + conn, err := client.Dial(context.Background(), lnInsecure.Multiaddr(), serverID) + require.NoError(t, err) + defer conn.Close() + require.Equal(t, lastComponent(t, conn.RemoteMultiaddr()).String(), wsma.String()) + require.Equal(t, lastComponent(t, conn.LocalMultiaddr()).String(), wsma.String()) + + // dialing the secure address should fail + _, err = client.Dial(context.Background(), lnSecure.Multiaddr(), serverID) + require.NoError(t, err) + }) + + t.Run("secure", func(t *testing.T) { + _, clientUpgrader := newUpgrader(t) + client, err := New(clientUpgrader, network.NullResourceManager, WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true})) + require.NoError(t, err) + + // dialing the insecure address should succeed + conn, err := client.Dial(context.Background(), lnSecure.Multiaddr(), serverID) + require.NoError(t, err) + defer conn.Close() + require.Equal(t, lastComponent(t, conn.RemoteMultiaddr()), wssma) + require.Equal(t, lastComponent(t, conn.LocalMultiaddr()), wssma) + + // dialing the insecure address should fail + _, err = client.Dial(context.Background(), lnInsecure.Multiaddr(), serverID) + require.NoError(t, err) + }) } func TestConcurrentClose(t *testing.T) { _, u := newUpgrader(t) tpt, err := New(u, network.NullResourceManager) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) l, err := tpt.maListen(ma.StringCast("/ip4/127.0.0.1/tcp/0/ws")) if err != nil { t.Fatal(err) From 67782ffa1c31ab41dbdcdce7a969f982c4dc2ca1 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 21 Feb 2022 11:46:54 +0400 Subject: [PATCH 92/93] don't resolve dns addresses for Listener.Multiaddr (#117) --- p2p/transport/websocket/addrs_test.go | 14 ++++++++++++++ p2p/transport/websocket/listener.go | 7 +++++++ 2 files changed, 21 insertions(+) diff --git a/p2p/transport/websocket/addrs_test.go b/p2p/transport/websocket/addrs_test.go index 1a73c28762..e2779fd462 100644 --- a/p2p/transport/websocket/addrs_test.go +++ b/p2p/transport/websocket/addrs_test.go @@ -4,6 +4,8 @@ import ( "net/url" "testing" + "github.com/stretchr/testify/require" + ma "github.com/multiformats/go-multiaddr" ) @@ -65,3 +67,15 @@ func TestConvertWebsocketMultiaddrToNetAddr(t *testing.T) { t.Fatalf("expected network: \"websocket\", got \"%s\"", wsaddr.Network()) } } + +func TestListeningOnDNSAddr(t *testing.T) { + ln, err := newListener(ma.StringCast("/dns/localhost/tcp/0/ws"), nil) + require.NoError(t, err) + addr := ln.Multiaddr() + first, rest := ma.SplitFirst(addr) + require.Equal(t, first.Protocol().Code, ma.P_DNS) + require.Equal(t, first.Value(), "localhost") + next, _ := ma.SplitFirst(rest) + require.Equal(t, next.Protocol().Code, ma.P_TCP) + require.NotEqual(t, next.Value(), "0") +} diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go index 24c79f9d17..b94bed798c 100644 --- a/p2p/transport/websocket/listener.go +++ b/p2p/transport/websocket/listener.go @@ -47,6 +47,13 @@ func newListener(a ma.Multiaddr, tlsConf *tls.Config) (*listener, error) { if err != nil { return nil, err } + first, _ := ma.SplitFirst(a) + // Don't resolve dns addresses. + // We want to be able to announce domain names, so the peer can validate the TLS certificate. + if c := first.Protocol().Code; c == ma.P_DNS || c == ma.P_DNS4 || c == ma.P_DNS6 || c == ma.P_DNSADDR { + _, last := ma.SplitFirst(laddr) + laddr = first.Encapsulate(last) + } ln := &listener{ nl: nl, From 690a16ccbe1a247341561dd9d6600e5680f529bb Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 22 Apr 2022 15:49:01 +0100 Subject: [PATCH 93/93] switch from github.com/libp2p/go-ws-transport to p2p/transport/websocket --- defaults.go | 2 +- go.mod | 3 +-- go.sum | 3 --- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/defaults.go b/defaults.go index a3a4c13236..a4a446cb54 100644 --- a/defaults.go +++ b/defaults.go @@ -7,6 +7,7 @@ import ( "github.com/libp2p/go-libp2p/p2p/net/connmgr" "github.com/libp2p/go-libp2p/p2p/transport/tcp" + ws "github.com/libp2p/go-libp2p/p2p/transport/websocket" "github.com/libp2p/go-libp2p-core/crypto" @@ -16,7 +17,6 @@ import ( rcmgr "github.com/libp2p/go-libp2p-resource-manager" tls "github.com/libp2p/go-libp2p-tls" yamux "github.com/libp2p/go-libp2p-yamux" - ws "github.com/libp2p/go-ws-transport" "github.com/multiformats/go-multiaddr" ) diff --git a/go.mod b/go.mod index b1f33b45fb..58926c36c6 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/benbjohnson/clock v1.3.0 github.com/gogo/protobuf v1.3.2 github.com/golang/mock v1.6.0 + github.com/gorilla/websocket v1.5.0 github.com/hashicorp/golang-lru v0.5.4 github.com/ipfs/go-cid v0.1.0 github.com/ipfs/go-datastore v0.5.1 @@ -32,7 +33,6 @@ require ( github.com/libp2p/go-reuseport v0.1.0 github.com/libp2p/go-reuseport-transport v0.1.0 github.com/libp2p/go-stream-muxer-multistream v0.4.0 - github.com/libp2p/go-ws-transport v0.6.0 github.com/libp2p/zeroconf/v2 v2.1.1 github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b @@ -70,7 +70,6 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect github.com/huin/goupnp v1.0.3 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect diff --git a/go.sum b/go.sum index d67d449db7..a8dc2928ac 100644 --- a/go.sum +++ b/go.sum @@ -284,7 +284,6 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -517,8 +516,6 @@ github.com/libp2p/go-tcp-transport v0.4.0/go.mod h1:0y52Rwrn4076xdJYu/51/qJIdxz+ github.com/libp2p/go-tcp-transport v0.5.0/go.mod h1:UPPL0DIjQqiWRwVAb+CEQlaAG0rp/mCqJfIhFcLHc4Y= github.com/libp2p/go-tcp-transport v0.5.1 h1:edOOs688VLZAozWC7Kj5/6HHXKNwi9M6wgRmmLa8M6Q= github.com/libp2p/go-tcp-transport v0.5.1/go.mod h1:UPPL0DIjQqiWRwVAb+CEQlaAG0rp/mCqJfIhFcLHc4Y= -github.com/libp2p/go-ws-transport v0.6.0 h1:326XBL6Q+5CQ2KtjXz32+eGu02W/Kz2+Fm4SpXdr0q4= -github.com/libp2p/go-ws-transport v0.6.0/go.mod h1:dXqtI9e2JV9FtF1NOtWVZSKXh5zXvnuwPXfj8GPBbYU= github.com/libp2p/go-yamux v1.4.1 h1:P1Fe9vF4th5JOxxgQvfbOHkrGqIZniTLf+ddhZp8YTI= github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux/v3 v3.0.1/go.mod h1:s2LsDhHbh+RfCsQoICSYt58U2f8ijtPANFD8BmE74Bo=