From 36c695a63fe2e47612854172352a496c6de4cce8 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 7 Mar 2017 17:16:30 +0700 Subject: [PATCH 001/138] initial commmit --- p2p/transport/quic/dialer.go | 24 ++++++++++++++++++++++++ p2p/transport/quic/listener.go | 28 ++++++++++++++++++++++++++++ p2p/transport/quic/transport.go | 26 ++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 p2p/transport/quic/dialer.go create mode 100644 p2p/transport/quic/listener.go create mode 100644 p2p/transport/quic/transport.go diff --git a/p2p/transport/quic/dialer.go b/p2p/transport/quic/dialer.go new file mode 100644 index 0000000000..a5536d4f49 --- /dev/null +++ b/p2p/transport/quic/dialer.go @@ -0,0 +1,24 @@ +package libp2pquic + +import ( + "context" + + tpt "github.com/libp2p/go-libp2p-transport" + ma "github.com/multiformats/go-multiaddr" +) + +type dialer struct{} + +func (d *dialer) Dial(raddr ma.Multiaddr) (tpt.Conn, error) { + panic("not implemented") +} + +func (d *dialer) DialContext(ctx context.Context, raddr ma.Multiaddr) (tpt.Conn, error) { + panic("not implemented") +} + +func (d *dialer) Matches(ma.Multiaddr) bool { + panic("not implemented") +} + +var _ tpt.Dialer = &dialer{} diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go new file mode 100644 index 0000000000..bf867770e4 --- /dev/null +++ b/p2p/transport/quic/listener.go @@ -0,0 +1,28 @@ +package libp2pquic + +import ( + "net" + + tpt "github.com/libp2p/go-libp2p-transport" + ma "github.com/multiformats/go-multiaddr" +) + +type listener struct{} + +func (l *listener) Accept() (tpt.Conn, error) { + panic("not implemented") +} + +func (l *listener) Close() error { + panic("not implemented") +} + +func (l *listener) Addr() net.Addr { + panic("not implemented") +} + +func (l *listener) Multiaddr() ma.Multiaddr { + panic("not implemented") +} + +var _ tpt.Listener = &listener{} diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go new file mode 100644 index 0000000000..8a35b4077d --- /dev/null +++ b/p2p/transport/quic/transport.go @@ -0,0 +1,26 @@ +package libp2pquic + +import ( + tpt "github.com/libp2p/go-libp2p-transport" + ma "github.com/multiformats/go-multiaddr" +) + +type QuicTransport struct{} + +func NewQuicTransport() *QuicTransport { + return &QuicTransport{} +} + +func (t *QuicTransport) Dialer(laddr ma.Multiaddr, opts ...tpt.DialOpt) (tpt.Dialer, error) { + panic("not implemented") +} + +func (t *QuicTransport) Listen(laddr ma.Multiaddr) (tpt.Listener, error) { + panic("not implemented") +} + +func (t *QuicTransport) Matches(ma.Multiaddr) bool { + panic("not implemented") +} + +var _ tpt.Transport = &QuicTransport{} From 67700695b0c165c1ac342b4dabf2082f674c4685 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 15 Mar 2017 19:41:08 +0700 Subject: [PATCH 002/138] implement the listener --- p2p/transport/quic/conn.go | 60 +++++++++++++++++++++++++++++++++ p2p/transport/quic/listener.go | 43 ++++++++++++++++++++--- p2p/transport/quic/transport.go | 39 ++++++++++++++++++--- 3 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 p2p/transport/quic/conn.go diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go new file mode 100644 index 0000000000..f9340ad2c9 --- /dev/null +++ b/p2p/transport/quic/conn.go @@ -0,0 +1,60 @@ +package libp2pquic + +import ( + "net" + "time" + + tpt "github.com/libp2p/go-libp2p-transport" + ma "github.com/multiformats/go-multiaddr" +) + +type conn struct { + quicConn net.Conn + transport tpt.Transport +} + +func (c *conn) Read(p []byte) (int, error) { + return c.quicConn.Read(p) +} + +func (c *conn) Write(p []byte) (int, error) { + return c.quicConn.Write(p) +} + +func (c *conn) Close() error { + return c.quicConn.Close() +} + +func (c *conn) LocalAddr() net.Addr { + return c.quicConn.LocalAddr() +} + +func (c *conn) RemoteAddr() net.Addr { + return c.quicConn.RemoteAddr() +} + +func (c *conn) LocalMultiaddr() ma.Multiaddr { + panic("not implemented") +} + +func (c *conn) RemoteMultiaddr() ma.Multiaddr { + panic("not implemented") +} + +func (c *conn) Transport() tpt.Transport { + return c.transport +} + +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 +} + +var _ tpt.Conn = &conn{} diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index bf867770e4..a0aea58e02 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -1,28 +1,61 @@ package libp2pquic import ( + "crypto/tls" "net" + pstore "github.com/libp2p/go-libp2p-peerstore" tpt "github.com/libp2p/go-libp2p-transport" + quicconn "github.com/marten-seemann/quic-conn" ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" ) -type listener struct{} +type listener struct { + laddr ma.Multiaddr + quicListener net.Listener + + transport tpt.Transport +} + +func newListener(laddr ma.Multiaddr, peers pstore.Peerstore, transport tpt.Transport) (*listener, error) { + tlsConf := &tls.Config{} + network, host, err := manet.DialArgs(laddr) + if err != nil { + return nil, err + } + qln, err := quicconn.Listen(network, host, tlsConf) + if err != nil { + return nil, err + } + return &listener{ + laddr: laddr, + quicListener: qln, + transport: transport, + }, nil +} func (l *listener) Accept() (tpt.Conn, error) { - panic("not implemented") + c, err := l.quicListener.Accept() + if err != nil { + return nil, err + } + return &conn{ + quicConn: c, + transport: l.transport, + }, nil } func (l *listener) Close() error { - panic("not implemented") + return l.quicListener.Close() } func (l *listener) Addr() net.Addr { - panic("not implemented") + return l.quicListener.Addr() } func (l *listener) Multiaddr() ma.Multiaddr { - panic("not implemented") + return l.laddr } var _ tpt.Listener = &listener{} diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 8a35b4077d..1982e9c053 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -1,22 +1,53 @@ package libp2pquic import ( + "sync" + + pstore "github.com/libp2p/go-libp2p-peerstore" tpt "github.com/libp2p/go-libp2p-transport" ma "github.com/multiformats/go-multiaddr" ) -type QuicTransport struct{} +// QuicTransport implements a QUIC Transport +type QuicTransport struct { + mutex sync.Mutex + + peers pstore.Peerstore + + listeners map[string]tpt.Listener +} -func NewQuicTransport() *QuicTransport { - return &QuicTransport{} +// NewQuicTransport creates a new QUIC Transport +// it tracks dialers and listeners created +func NewQuicTransport(peers pstore.Peerstore) *QuicTransport { + return &QuicTransport{ + peers: peers, + listeners: make(map[string]tpt.Listener), + } } func (t *QuicTransport) Dialer(laddr ma.Multiaddr, opts ...tpt.DialOpt) (tpt.Dialer, error) { panic("not implemented") } +// Listen starts listening on laddr func (t *QuicTransport) Listen(laddr ma.Multiaddr) (tpt.Listener, error) { - panic("not implemented") + // TODO: check if laddr is actually a QUIC address + t.mutex.Lock() + defer t.mutex.Unlock() + + l, ok := t.listeners[laddr.String()] + if ok { + return l, nil + } + + ln, err := newListener(laddr, t.peers, t) + if err != nil { + return nil, err + } + + t.listeners[laddr.String()] = ln + return ln, nil } func (t *QuicTransport) Matches(ma.Multiaddr) bool { From 3b09dd9ce2c061f72cc3e3dce176d040dbc47771 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 17 Mar 2017 16:57:06 +0700 Subject: [PATCH 003/138] use conn wrapper functions to construct a go-libp2p-transport.Conn --- p2p/transport/quic/conn.go | 60 ---------------------------------- p2p/transport/quic/listener.go | 12 +++++-- 2 files changed, 9 insertions(+), 63 deletions(-) delete mode 100644 p2p/transport/quic/conn.go diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go deleted file mode 100644 index f9340ad2c9..0000000000 --- a/p2p/transport/quic/conn.go +++ /dev/null @@ -1,60 +0,0 @@ -package libp2pquic - -import ( - "net" - "time" - - tpt "github.com/libp2p/go-libp2p-transport" - ma "github.com/multiformats/go-multiaddr" -) - -type conn struct { - quicConn net.Conn - transport tpt.Transport -} - -func (c *conn) Read(p []byte) (int, error) { - return c.quicConn.Read(p) -} - -func (c *conn) Write(p []byte) (int, error) { - return c.quicConn.Write(p) -} - -func (c *conn) Close() error { - return c.quicConn.Close() -} - -func (c *conn) LocalAddr() net.Addr { - return c.quicConn.LocalAddr() -} - -func (c *conn) RemoteAddr() net.Addr { - return c.quicConn.RemoteAddr() -} - -func (c *conn) LocalMultiaddr() ma.Multiaddr { - panic("not implemented") -} - -func (c *conn) RemoteMultiaddr() ma.Multiaddr { - panic("not implemented") -} - -func (c *conn) Transport() tpt.Transport { - return c.transport -} - -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 -} - -var _ tpt.Conn = &conn{} diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index a0aea58e02..9ef7386bd0 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -40,9 +40,15 @@ func (l *listener) Accept() (tpt.Conn, error) { if err != nil { return nil, err } - return &conn{ - quicConn: c, - transport: l.transport, + + mnc, err := manet.WrapNetConn(c) + if err != nil { + return nil, err + } + + return &tpt.ConnWrap{ + Conn: mnc, + Tpt: l.transport, }, nil } From 260693d121cef99cf0dedb5179e9ed1c29fe9ae1 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 18 Mar 2017 13:43:54 +0700 Subject: [PATCH 004/138] add (empty) test suite and travis config --- p2p/transport/quic/libp2pquic_suite_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 p2p/transport/quic/libp2pquic_suite_test.go diff --git a/p2p/transport/quic/libp2pquic_suite_test.go b/p2p/transport/quic/libp2pquic_suite_test.go new file mode 100644 index 0000000000..d3766da3e0 --- /dev/null +++ b/p2p/transport/quic/libp2pquic_suite_test.go @@ -0,0 +1,13 @@ +package libp2pquic + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestQuicGo(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "libp2p QUIC Transport Suite") +} From a06bda1f4ee1fcca6d0372718440c298d6c2c0dd Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 18 Mar 2017 15:20:41 +0700 Subject: [PATCH 005/138] implement transport.Matches --- p2p/transport/quic/transport.go | 5 +++-- p2p/transport/quic/transport_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 p2p/transport/quic/transport_test.go diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 1982e9c053..cfd61f01b4 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -6,6 +6,7 @@ import ( pstore "github.com/libp2p/go-libp2p-peerstore" tpt "github.com/libp2p/go-libp2p-transport" ma "github.com/multiformats/go-multiaddr" + "github.com/whyrusleeping/mafmt" ) // QuicTransport implements a QUIC Transport @@ -50,8 +51,8 @@ func (t *QuicTransport) Listen(laddr ma.Multiaddr) (tpt.Listener, error) { return ln, nil } -func (t *QuicTransport) Matches(ma.Multiaddr) bool { - panic("not implemented") +func (t *QuicTransport) Matches(a ma.Multiaddr) bool { + return mafmt.QUIC.Matches(a) } var _ tpt.Transport = &QuicTransport{} diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go new file mode 100644 index 0000000000..a6cd01255d --- /dev/null +++ b/p2p/transport/quic/transport_test.go @@ -0,0 +1,25 @@ +package libp2pquic + +import ( + ma "github.com/multiformats/go-multiaddr" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Transport", func() { + var t *QuicTransport + + BeforeEach(func() { + t = NewQuicTransport(nil) + }) + + It("matches", func() { + invalidAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234") + Expect(err).ToNot(HaveOccurred()) + validAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234/quic") + Expect(err).ToNot(HaveOccurred()) + Expect(t.Matches(invalidAddr)).To(BeFalse()) + Expect(t.Matches(validAddr)).To(BeTrue()) + }) +}) From 9fafe5c100ab0ebf5ca423a832ab19babef37d40 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 18 Mar 2017 22:55:51 +0700 Subject: [PATCH 006/138] add tests for the listener --- p2p/transport/quic/listener_test.go | 105 ++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 p2p/transport/quic/listener_test.go diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go new file mode 100644 index 0000000000..9b1851a5e0 --- /dev/null +++ b/p2p/transport/quic/listener_test.go @@ -0,0 +1,105 @@ +package libp2pquic + +import ( + "errors" + "net" + + tpt "github.com/libp2p/go-libp2p-transport" + ma "github.com/multiformats/go-multiaddr" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +type mockNetListener struct { + connToAccept net.Conn + acceptErr error + closed bool +} + +func (m *mockNetListener) Accept() (net.Conn, error) { + if m.acceptErr != nil { + return nil, m.acceptErr + } + return m.connToAccept, nil +} + +func (m *mockNetListener) Close() error { + m.closed = true + return nil +} + +func (m *mockNetListener) Addr() net.Addr { + panic("not implemented") +} + +var _ net.Listener = &mockNetListener{} + +var _ = Describe("Listener", func() { + var ( + l *listener + netListener *mockNetListener + transport tpt.Transport + ) + + BeforeEach(func() { + netListener = &mockNetListener{} + transport = &QuicTransport{} + l = &listener{ + quicListener: netListener, + transport: transport, + } + }) + + It("returns its addr", func() { + laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/12345/quic") + Expect(err).ToNot(HaveOccurred()) + l, err = newListener(laddr, nil, nil) + Expect(err).ToNot(HaveOccurred()) + Expect(l.Addr().String()).To(Equal("127.0.0.1:12345")) + }) + + It("returns its multiaddr", func() { + laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/12346/quic") + Expect(err).ToNot(HaveOccurred()) + l, err = newListener(laddr, nil, nil) + Expect(err).ToNot(HaveOccurred()) + Expect(l.Multiaddr().String()).To(Equal("/ip4/127.0.0.1/udp/12346/quic")) + }) + + It("closes", func() { + err := l.Close() + Expect(err).ToNot(HaveOccurred()) + Expect(netListener.closed).To(BeTrue()) + }) + + Context("accepting", func() { + It("accepts a new conn", func() { + remoteAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:1234") + Expect(err).ToNot(HaveOccurred()) + localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:4321") + Expect(err).ToNot(HaveOccurred()) + udpConn, err := net.DialUDP("udp", localAddr, remoteAddr) + netListener.connToAccept = udpConn + conn, err := l.Accept() + Expect(err).ToNot(HaveOccurred()) + Expect(conn.LocalMultiaddr().String()).To(Equal("/ip4/127.0.0.1/udp/4321")) + Expect(conn.RemoteMultiaddr().String()).To(Equal("/ip4/127.0.0.1/udp/1234")) + Expect(conn.Transport()).To(Equal(transport)) + }) + + It("errors if it can't read the muliaddresses of a conn", func() { + netListener.connToAccept = &net.UDPConn{} + _, err := l.Accept() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("nil multiaddr")) + }) + + It("errors if no connection can be accepted", func() { + testErr := errors.New("test error") + netListener.acceptErr = testErr + _, err := l.Accept() + Expect(err).To(MatchError(testErr)) + }) + }) +}) From 8ad1607e8071d35fdd4272f3d20d88e09584b780 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 18 Mar 2017 23:06:50 +0700 Subject: [PATCH 007/138] add tests for transport.Listen --- p2p/transport/quic/transport_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go index a6cd01255d..278f77129f 100644 --- a/p2p/transport/quic/transport_test.go +++ b/p2p/transport/quic/transport_test.go @@ -14,6 +14,28 @@ var _ = Describe("Transport", func() { t = NewQuicTransport(nil) }) + Context("listening", func() { + It("creates a new listener", func() { + maddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234/quic") + Expect(err).ToNot(HaveOccurred()) + ln, err := t.Listen(maddr) + Expect(err).ToNot(HaveOccurred()) + Expect(ln.Multiaddr()).To(Equal(maddr)) + }) + + It("returns an existing listener", func() { + maddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1235/quic") + Expect(err).ToNot(HaveOccurred()) + ln, err := t.Listen(maddr) + Expect(err).ToNot(HaveOccurred()) + Expect(ln.Multiaddr()).To(Equal(maddr)) + ln2, err := t.Listen(maddr) + Expect(err).ToNot(HaveOccurred()) + Expect(ln2).To(Equal(ln)) + Expect(t.listeners).To(HaveLen(1)) + }) + }) + It("matches", func() { invalidAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234") Expect(err).ToNot(HaveOccurred()) From 4ebce2621bcdbef1340361813711bcc508f53632 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 18 Mar 2017 23:23:13 +0700 Subject: [PATCH 008/138] reject non-QUIC listen addresses --- p2p/transport/quic/transport.go | 6 +++++- p2p/transport/quic/transport_test.go | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index cfd61f01b4..96465e11cd 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -1,6 +1,7 @@ package libp2pquic import ( + "fmt" "sync" pstore "github.com/libp2p/go-libp2p-peerstore" @@ -33,7 +34,10 @@ func (t *QuicTransport) Dialer(laddr ma.Multiaddr, opts ...tpt.DialOpt) (tpt.Dia // Listen starts listening on laddr func (t *QuicTransport) Listen(laddr ma.Multiaddr) (tpt.Listener, error) { - // TODO: check if laddr is actually a QUIC address + if !t.Matches(laddr) { + return nil, fmt.Errorf("quic transport cannot listen on %q", laddr) + } + t.mutex.Lock() defer t.mutex.Unlock() diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go index 278f77129f..efe1b169e0 100644 --- a/p2p/transport/quic/transport_test.go +++ b/p2p/transport/quic/transport_test.go @@ -34,6 +34,13 @@ var _ = Describe("Transport", func() { Expect(ln2).To(Equal(ln)) Expect(t.listeners).To(HaveLen(1)) }) + + It("errors if the address is not a QUIC address", func() { + maddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1235/utp") + Expect(err).ToNot(HaveOccurred()) + _, err = t.Listen(maddr) + Expect(err).To(MatchError("quic transport cannot listen on \"/ip4/127.0.0.1/udp/1235/utp\"")) + }) }) It("matches", func() { From 147f9baf98428f8bb19f177e51051490ce68a1fc Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 20 Mar 2017 14:53:30 +0700 Subject: [PATCH 009/138] initialize the listener with a valid TLS config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The certificate doesn’t matter, since it is not validated anywehere (yet), but we need to send something. --- p2p/transport/quic/listener.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 9ef7386bd0..99eb96a5e7 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -1,11 +1,11 @@ package libp2pquic import ( - "crypto/tls" "net" pstore "github.com/libp2p/go-libp2p-peerstore" tpt "github.com/libp2p/go-libp2p-transport" + testdata "github.com/lucas-clemente/quic-go/testdata" quicconn "github.com/marten-seemann/quic-conn" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" @@ -19,7 +19,9 @@ type listener struct { } func newListener(laddr ma.Multiaddr, peers pstore.Peerstore, transport tpt.Transport) (*listener, error) { - tlsConf := &tls.Config{} + // we need to provide a certificate here + // use the demo certificate from quic-go + tlsConf := testdata.GetTLSConfig() network, host, err := manet.DialArgs(laddr) if err != nil { return nil, err From 960bacbe0b3c902d120db466761528efef612ae6 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 20 Mar 2017 14:53:55 +0700 Subject: [PATCH 010/138] implement a dialer --- p2p/transport/quic/dialer.go | 41 +++++++++++++++++++--- p2p/transport/quic/dialer_test.go | 52 ++++++++++++++++++++++++++++ p2p/transport/quic/transport.go | 33 +++++++++++++++--- p2p/transport/quic/transport_test.go | 28 +++++++++++++++ 4 files changed, 144 insertions(+), 10 deletions(-) create mode 100644 p2p/transport/quic/dialer_test.go diff --git a/p2p/transport/quic/dialer.go b/p2p/transport/quic/dialer.go index a5536d4f49..f8e90ed93b 100644 --- a/p2p/transport/quic/dialer.go +++ b/p2p/transport/quic/dialer.go @@ -2,23 +2,54 @@ package libp2pquic import ( "context" + "crypto/tls" tpt "github.com/libp2p/go-libp2p-transport" + quicconn "github.com/marten-seemann/quic-conn" ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" + "github.com/whyrusleeping/mafmt" ) -type dialer struct{} +type dialer struct { + transport tpt.Transport +} + +func newDialer(transport tpt.Transport) (*dialer, error) { + return &dialer{ + transport: transport, + }, nil +} func (d *dialer) Dial(raddr ma.Multiaddr) (tpt.Conn, error) { - panic("not implemented") + // TODO: check that raddr is a QUIC address + tlsConf := &tls.Config{InsecureSkipVerify: true} + _, host, err := manet.DialArgs(raddr) + if err != nil { + return nil, err + } + c, err := quicconn.Dial(host, tlsConf) + if err != nil { + return nil, err + } + + mnc, err := manet.WrapNetConn(c) + if err != nil { + return nil, err + } + + return &tpt.ConnWrap{ + Conn: mnc, + Tpt: d.transport, + }, nil } func (d *dialer) DialContext(ctx context.Context, raddr ma.Multiaddr) (tpt.Conn, error) { - panic("not implemented") + return d.Dial(raddr) } -func (d *dialer) Matches(ma.Multiaddr) bool { - panic("not implemented") +func (d *dialer) Matches(a ma.Multiaddr) bool { + return mafmt.QUIC.Matches(a) } var _ tpt.Dialer = &dialer{} diff --git a/p2p/transport/quic/dialer_test.go b/p2p/transport/quic/dialer_test.go new file mode 100644 index 0000000000..f587cb65be --- /dev/null +++ b/p2p/transport/quic/dialer_test.go @@ -0,0 +1,52 @@ +package libp2pquic + +import ( + tpt "github.com/libp2p/go-libp2p-transport" + ma "github.com/multiformats/go-multiaddr" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Listener", func() { + var ( + d *dialer + transport tpt.Transport + ) + + BeforeEach(func() { + var err error + transport = &QuicTransport{} + d, err = newDialer(transport) + Expect(err).ToNot(HaveOccurred()) + }) + + It("dials", func() { + addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/8888") + Expect(err).ToNot(HaveOccurred()) + + // start a listener to connect to + var ln *listener + go func() { + defer GinkgoRecover() + ln, err = newListener(addr, nil, transport) + Expect(err).ToNot(HaveOccurred()) + _, err = ln.Accept() + Expect(err).ToNot(HaveOccurred()) + }() + + Eventually(func() *listener { return ln }).ShouldNot(BeNil()) + conn, err := d.Dial(addr) + Expect(err).ToNot(HaveOccurred()) + Expect(conn.Transport()).To(Equal(d.transport)) + }) + + It("matches", func() { + invalidAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234") + Expect(err).ToNot(HaveOccurred()) + validAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234/quic") + Expect(err).ToNot(HaveOccurred()) + Expect(d.Matches(invalidAddr)).To(BeFalse()) + Expect(d.Matches(validAddr)).To(BeTrue()) + }) +}) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 96465e11cd..68968695f8 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -12,24 +12,47 @@ import ( // QuicTransport implements a QUIC Transport type QuicTransport struct { - mutex sync.Mutex - peers pstore.Peerstore + lmutex sync.Mutex listeners map[string]tpt.Listener + + dmutex sync.Mutex + dialers map[string]tpt.Dialer } // NewQuicTransport creates a new QUIC Transport // it tracks dialers and listeners created func NewQuicTransport(peers pstore.Peerstore) *QuicTransport { + // utils.SetLogLevel(utils.LogLevelDebug) return &QuicTransport{ peers: peers, listeners: make(map[string]tpt.Listener), + dialers: make(map[string]tpt.Dialer), } } func (t *QuicTransport) Dialer(laddr ma.Multiaddr, opts ...tpt.DialOpt) (tpt.Dialer, error) { - panic("not implemented") + if !t.Matches(laddr) { + return nil, fmt.Errorf("quic transport cannot dial %q", laddr) + } + + t.dmutex.Lock() + defer t.dmutex.Unlock() + + s := laddr.String() + d, ok := t.dialers[s] + if ok { + return d, nil + } + + // TODO: read opts + quicd, err := newDialer(t) + if err != nil { + return nil, err + } + t.dialers[s] = quicd + return quicd, nil } // Listen starts listening on laddr @@ -38,8 +61,8 @@ func (t *QuicTransport) Listen(laddr ma.Multiaddr) (tpt.Listener, error) { return nil, fmt.Errorf("quic transport cannot listen on %q", laddr) } - t.mutex.Lock() - defer t.mutex.Unlock() + t.lmutex.Lock() + defer t.lmutex.Unlock() l, ok := t.listeners[laddr.String()] if ok { diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go index efe1b169e0..d7f8358c84 100644 --- a/p2p/transport/quic/transport_test.go +++ b/p2p/transport/quic/transport_test.go @@ -43,6 +43,34 @@ var _ = Describe("Transport", func() { }) }) + Context("dialing", func() { + It("creates a new dialer", func() { + maddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234/quic") + Expect(err).ToNot(HaveOccurred()) + d, err := t.Dialer(maddr) + Expect(err).ToNot(HaveOccurred()) + Expect(d).ToNot(BeNil()) + }) + + It("returns an existing dialer", func() { + maddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1235/quic") + Expect(err).ToNot(HaveOccurred()) + d, err := t.Dialer(maddr) + Expect(err).ToNot(HaveOccurred()) + d2, err := t.Dialer(maddr) + Expect(err).ToNot(HaveOccurred()) + Expect(d2).To(Equal(d)) + Expect(t.dialers).To(HaveLen(1)) + }) + + It("errors if the address is not a QUIC address", func() { + maddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1235/utp") + Expect(err).ToNot(HaveOccurred()) + _, err = t.Dialer(maddr) + Expect(err).To(MatchError("quic transport cannot dial \"/ip4/127.0.0.1/udp/1235/utp\"")) + }) + }) + It("matches", func() { invalidAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234") Expect(err).ToNot(HaveOccurred()) From cb1f88a8c037e7812cf75c82d37c7135f8e0ae68 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 12 Apr 2017 08:53:56 +0700 Subject: [PATCH 011/138] remove unused peerstore from listener --- p2p/transport/quic/dialer_test.go | 2 +- p2p/transport/quic/listener.go | 3 +-- p2p/transport/quic/listener_test.go | 4 ++-- p2p/transport/quic/transport.go | 8 ++------ p2p/transport/quic/transport_test.go | 2 +- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/p2p/transport/quic/dialer_test.go b/p2p/transport/quic/dialer_test.go index f587cb65be..229e19a606 100644 --- a/p2p/transport/quic/dialer_test.go +++ b/p2p/transport/quic/dialer_test.go @@ -29,7 +29,7 @@ var _ = Describe("Listener", func() { var ln *listener go func() { defer GinkgoRecover() - ln, err = newListener(addr, nil, transport) + ln, err = newListener(addr, transport) Expect(err).ToNot(HaveOccurred()) _, err = ln.Accept() Expect(err).ToNot(HaveOccurred()) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 99eb96a5e7..4ca8487782 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -3,7 +3,6 @@ package libp2pquic import ( "net" - pstore "github.com/libp2p/go-libp2p-peerstore" tpt "github.com/libp2p/go-libp2p-transport" testdata "github.com/lucas-clemente/quic-go/testdata" quicconn "github.com/marten-seemann/quic-conn" @@ -18,7 +17,7 @@ type listener struct { transport tpt.Transport } -func newListener(laddr ma.Multiaddr, peers pstore.Peerstore, transport tpt.Transport) (*listener, error) { +func newListener(laddr ma.Multiaddr, transport tpt.Transport) (*listener, error) { // we need to provide a certificate here // use the demo certificate from quic-go tlsConf := testdata.GetTLSConfig() diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index 9b1851a5e0..6c99e35b9f 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -54,7 +54,7 @@ var _ = Describe("Listener", func() { It("returns its addr", func() { laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/12345/quic") Expect(err).ToNot(HaveOccurred()) - l, err = newListener(laddr, nil, nil) + l, err = newListener(laddr, nil) Expect(err).ToNot(HaveOccurred()) Expect(l.Addr().String()).To(Equal("127.0.0.1:12345")) }) @@ -62,7 +62,7 @@ var _ = Describe("Listener", func() { It("returns its multiaddr", func() { laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/12346/quic") Expect(err).ToNot(HaveOccurred()) - l, err = newListener(laddr, nil, nil) + l, err = newListener(laddr, nil) Expect(err).ToNot(HaveOccurred()) Expect(l.Multiaddr().String()).To(Equal("/ip4/127.0.0.1/udp/12346/quic")) }) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 68968695f8..7505f94a6d 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -4,7 +4,6 @@ import ( "fmt" "sync" - pstore "github.com/libp2p/go-libp2p-peerstore" tpt "github.com/libp2p/go-libp2p-transport" ma "github.com/multiformats/go-multiaddr" "github.com/whyrusleeping/mafmt" @@ -12,8 +11,6 @@ import ( // QuicTransport implements a QUIC Transport type QuicTransport struct { - peers pstore.Peerstore - lmutex sync.Mutex listeners map[string]tpt.Listener @@ -23,10 +20,9 @@ type QuicTransport struct { // NewQuicTransport creates a new QUIC Transport // it tracks dialers and listeners created -func NewQuicTransport(peers pstore.Peerstore) *QuicTransport { +func NewQuicTransport() *QuicTransport { // utils.SetLogLevel(utils.LogLevelDebug) return &QuicTransport{ - peers: peers, listeners: make(map[string]tpt.Listener), dialers: make(map[string]tpt.Dialer), } @@ -69,7 +65,7 @@ func (t *QuicTransport) Listen(laddr ma.Multiaddr) (tpt.Listener, error) { return l, nil } - ln, err := newListener(laddr, t.peers, t) + ln, err := newListener(laddr, t) if err != nil { return nil, err } diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go index d7f8358c84..141e468426 100644 --- a/p2p/transport/quic/transport_test.go +++ b/p2p/transport/quic/transport_test.go @@ -11,7 +11,7 @@ var _ = Describe("Transport", func() { var t *QuicTransport BeforeEach(func() { - t = NewQuicTransport(nil) + t = NewQuicTransport() }) Context("listening", func() { From 0a1f5c07307597523809285fc36b289d5a6b0621 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 17 May 2017 16:57:51 +0800 Subject: [PATCH 012/138] add support for multi-stream connections --- p2p/transport/quic/conn.go | 113 ++++++++++++++++++++ p2p/transport/quic/conn_test.go | 159 ++++++++++++++++++++++++++++ p2p/transport/quic/dialer.go | 18 ++-- p2p/transport/quic/listener.go | 39 +++---- p2p/transport/quic/listener_test.go | 61 +++-------- p2p/transport/quic/stream.go | 29 +++++ p2p/transport/quic/transport.go | 2 + 7 files changed, 343 insertions(+), 78 deletions(-) create mode 100644 p2p/transport/quic/conn.go create mode 100644 p2p/transport/quic/conn_test.go create mode 100644 p2p/transport/quic/stream.go diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go new file mode 100644 index 0000000000..1239e191e9 --- /dev/null +++ b/p2p/transport/quic/conn.go @@ -0,0 +1,113 @@ +package libp2pquic + +import ( + "fmt" + "net" + + smux "github.com/jbenet/go-stream-muxer" + tpt "github.com/libp2p/go-libp2p-transport" + quic "github.com/lucas-clemente/quic-go" + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" +) + +type quicConn struct { + sess quic.Session + transport tpt.Transport + + laddr ma.Multiaddr + raddr ma.Multiaddr +} + +var _ tpt.Conn = &quicConn{} +var _ tpt.MultiStreamConn = &quicConn{} + +func newQuicConn(sess quic.Session, t tpt.Transport) (*quicConn, error) { + // analogues to manet.WrapNetConn + + laddr, err := quicMultiAddress(sess.LocalAddr()) + if err != nil { + return nil, fmt.Errorf("failed to convert nconn.LocalAddr: %s", err) + } + + // analogues to manet.WrapNetConn + raddr, err := quicMultiAddress(sess.RemoteAddr()) + if err != nil { + return nil, fmt.Errorf("failed to convert nconn.RemoteAddr: %s", err) + } + + return &quicConn{ + sess: sess, + laddr: laddr, + raddr: raddr, + transport: t, + }, nil +} + +func (c *quicConn) AcceptStream() (smux.Stream, error) { + str, err := c.sess.AcceptStream() + if err != nil { + return nil, err + } + return &quicStream{Stream: str}, nil +} + +func (c *quicConn) OpenStream() (smux.Stream, error) { + str, err := c.sess.OpenStream() + if err != nil { + return nil, err + } + return &quicStream{Stream: str}, nil +} + +func (c *quicConn) Serve(handler smux.StreamHandler) { + for { // accept loop + s, err := c.AcceptStream() + if err != nil { + return // err always means closed. + } + go handler(s) + } +} + +func (c *quicConn) Close() error { + return c.sess.Close(nil) +} + +// TODO: implement this +func (c *quicConn) IsClosed() bool { + return false +} + +func (c *quicConn) LocalAddr() net.Addr { + return c.sess.LocalAddr() +} + +func (c *quicConn) LocalMultiaddr() ma.Multiaddr { + return c.laddr +} + +func (c *quicConn) RemoteAddr() net.Addr { + return c.sess.RemoteAddr() +} + +func (c *quicConn) RemoteMultiaddr() ma.Multiaddr { + return c.raddr +} + +func (c *quicConn) Transport() tpt.Transport { + return c.transport +} + +// TODO: there must be a better way to do this +func quicMultiAddress(na net.Addr) (ma.Multiaddr, error) { + udpMA, err := manet.FromNetAddr(na) + if err != nil { + return nil, err + } + quicMA, err := ma.NewMultiaddr(udpMA.String() + "/quic") + if err != nil { + return nil, err + } + return quicMA, nil +} diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go new file mode 100644 index 0000000000..bfce79c965 --- /dev/null +++ b/p2p/transport/quic/conn_test.go @@ -0,0 +1,159 @@ +package libp2pquic + +import ( + "errors" + "net" + + smux "github.com/jbenet/go-stream-muxer" + quic "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/protocol" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +type mockStream struct { + id protocol.StreamID +} + +func (s *mockStream) Close() error { return nil } +func (s *mockStream) Reset(error) { return } +func (s *mockStream) Read([]byte) (int, error) { return 0, nil } +func (s *mockStream) Write([]byte) (int, error) { return 0, nil } +func (s *mockStream) StreamID() protocol.StreamID { return s.id } + +var _ quic.Stream = &mockStream{} + +type mockQuicSession struct { + closed bool + + localAddr net.Addr + remoteAddr net.Addr + + streamToAccept quic.Stream + streamAcceptErr error + + streamToOpen quic.Stream + streamOpenErr error +} + +var _ quic.Session = &mockQuicSession{} + +func (s *mockQuicSession) AcceptStream() (quic.Stream, error) { + return s.streamToAccept, s.streamAcceptErr +} +func (s *mockQuicSession) OpenStream() (quic.Stream, error) { return s.streamToOpen, s.streamOpenErr } +func (s *mockQuicSession) OpenStreamSync() (quic.Stream, error) { return s.streamToOpen, nil } +func (s *mockQuicSession) Close(error) error { s.closed = true; return nil } +func (s *mockQuicSession) LocalAddr() net.Addr { return s.localAddr } +func (s *mockQuicSession) RemoteAddr() net.Addr { return s.remoteAddr } + +var _ = Describe("Conn", func() { + var ( + conn *quicConn + sess *mockQuicSession + ) + + BeforeEach(func() { + sess = &mockQuicSession{ + localAddr: &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1337}, + remoteAddr: &net.UDPAddr{IP: net.IPv4(192, 168, 13, 37), Port: 1234}, + } + var err error + conn, err = newQuicConn(sess, nil) + Expect(err).ToNot(HaveOccurred()) + }) + + It("has the correct local address", func() { + Expect(conn.LocalAddr()).To(Equal(sess.localAddr)) + Expect(conn.LocalMultiaddr().String()).To(Equal("/ip4/127.0.0.1/udp/1337/quic")) + }) + + It("has the correct remote address", func() { + Expect(conn.RemoteAddr()).To(Equal(sess.remoteAddr)) + Expect(conn.RemoteMultiaddr().String()).To(Equal("/ip4/192.168.13.37/udp/1234/quic")) + }) + + It("closes", func() { + err := conn.Close() + Expect(err).ToNot(HaveOccurred()) + Expect(sess.closed).To(BeTrue()) + }) + + Context("opening streams", func() { + It("opens streams", func() { + s := &mockStream{id: 1337} + sess.streamToOpen = s + str, err := conn.OpenStream() + Expect(err).ToNot(HaveOccurred()) + Expect(str.(*quicStream).Stream).To(Equal(s)) + }) + + It("errors when it can't open a stream", func() { + testErr := errors.New("stream open err") + sess.streamOpenErr = testErr + _, err := conn.OpenStream() + Expect(err).To(MatchError(testErr)) + }) + }) + + Context("accepting streams", func() { + It("accepts streams", func() { + s := &mockStream{id: 1337} + sess.streamToAccept = s + str, err := conn.AcceptStream() + Expect(err).ToNot(HaveOccurred()) + Expect(str.(*quicStream).Stream).To(Equal(s)) + }) + + It("errors when it can't open a stream", func() { + testErr := errors.New("stream open err") + sess.streamAcceptErr = testErr + _, err := conn.AcceptStream() + Expect(err).To(MatchError(testErr)) + }) + }) + + Context("serving", func() { + var ( + handler func(smux.Stream) + handlerCalled bool + handlerCalledWith smux.Stream + ) + + BeforeEach(func() { + handlerCalled = false + handlerCalledWith = nil + handler = func(s smux.Stream) { + handlerCalledWith = s + handlerCalled = true + } + }) + + It("calls the handler", func() { + str := &mockStream{id: 5} + sess.streamToAccept = str + var returned bool + go func() { + conn.Serve(handler) + returned = true + }() + Eventually(func() bool { return handlerCalled }).Should(BeTrue()) + Expect(handlerCalledWith.(*quicStream).Stream).To(Equal(str)) + // make the go-routine return + sess.streamAcceptErr = errors.New("stop test") + }) + + It("returns when accepting a stream errors", func() { + sess.streamAcceptErr = errors.New("accept err") + var returned bool + go func() { + conn.Serve(handler) + returned = true + }() + Eventually(func() bool { return returned }).Should(BeTrue()) + Expect(handlerCalled).To(BeFalse()) + }) + }) + +}) diff --git a/p2p/transport/quic/dialer.go b/p2p/transport/quic/dialer.go index f8e90ed93b..8563674369 100644 --- a/p2p/transport/quic/dialer.go +++ b/p2p/transport/quic/dialer.go @@ -5,7 +5,7 @@ import ( "crypto/tls" tpt "github.com/libp2p/go-libp2p-transport" - quicconn "github.com/marten-seemann/quic-conn" + quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" "github.com/whyrusleeping/mafmt" @@ -15,6 +15,8 @@ type dialer struct { transport tpt.Transport } +var _ tpt.Dialer = &dialer{} + func newDialer(transport tpt.Transport) (*dialer, error) { return &dialer{ transport: transport, @@ -28,28 +30,20 @@ func (d *dialer) Dial(raddr ma.Multiaddr) (tpt.Conn, error) { if err != nil { return nil, err } - c, err := quicconn.Dial(host, tlsConf) - if err != nil { - return nil, err - } - mnc, err := manet.WrapNetConn(c) + qsess, err := quic.DialAddr(host, &quic.Config{TLSConfig: tlsConf}) if err != nil { return nil, err } - return &tpt.ConnWrap{ - Conn: mnc, - Tpt: d.transport, - }, nil + return newQuicConn(qsess, d.transport) } func (d *dialer) DialContext(ctx context.Context, raddr ma.Multiaddr) (tpt.Conn, error) { + // TODO: implement the ctx return d.Dial(raddr) } func (d *dialer) Matches(a ma.Multiaddr) bool { return mafmt.QUIC.Matches(a) } - -var _ tpt.Dialer = &dialer{} diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 4ca8487782..18ab38201c 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -4,53 +4,50 @@ import ( "net" tpt "github.com/libp2p/go-libp2p-transport" + quic "github.com/lucas-clemente/quic-go" testdata "github.com/lucas-clemente/quic-go/testdata" - quicconn "github.com/marten-seemann/quic-conn" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" ) type listener struct { laddr ma.Multiaddr - quicListener net.Listener + quicListener quic.Listener transport tpt.Transport } -func newListener(laddr ma.Multiaddr, transport tpt.Transport) (*listener, error) { - // we need to provide a certificate here - // use the demo certificate from quic-go - tlsConf := testdata.GetTLSConfig() - network, host, err := manet.DialArgs(laddr) +var _ tpt.Listener = &listener{} + +func newListener(laddr ma.Multiaddr, t tpt.Transport) (*listener, error) { + qconf := &quic.Config{ + // we need to provide a certificate here + // use the demo certificate from quic-go + TLSConfig: testdata.GetTLSConfig(), + } + + _, host, err := manet.DialArgs(laddr) if err != nil { return nil, err } - qln, err := quicconn.Listen(network, host, tlsConf) + qln, err := quic.ListenAddr(host, qconf) if err != nil { return nil, err } + return &listener{ laddr: laddr, quicListener: qln, - transport: transport, + transport: t, }, nil } func (l *listener) Accept() (tpt.Conn, error) { - c, err := l.quicListener.Accept() + sess, err := l.quicListener.Accept() if err != nil { return nil, err } - - mnc, err := manet.WrapNetConn(c) - if err != nil { - return nil, err - } - - return &tpt.ConnWrap{ - Conn: mnc, - Tpt: l.transport, - }, nil + return newQuicConn(sess, l.transport) } func (l *listener) Close() error { @@ -64,5 +61,3 @@ func (l *listener) Addr() net.Addr { func (l *listener) Multiaddr() ma.Multiaddr { return l.laddr } - -var _ tpt.Listener = &listener{} diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index 6c99e35b9f..726c54f5d7 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -5,48 +5,42 @@ import ( "net" tpt "github.com/libp2p/go-libp2p-transport" + quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) -type mockNetListener struct { +type mockQuicListener struct { connToAccept net.Conn - acceptErr error + serveErr error closed bool -} -func (m *mockNetListener) Accept() (net.Conn, error) { - if m.acceptErr != nil { - return nil, m.acceptErr - } - return m.connToAccept, nil + sessionToAccept quic.Session + acceptErr error } -func (m *mockNetListener) Close() error { - m.closed = true - return nil -} +var _ quic.Listener = &mockQuicListener{} + +func (m *mockQuicListener) Close() error { m.closed = true; return nil } +func (m *mockQuicListener) Accept() (quic.Session, error) { return m.sessionToAccept, m.acceptErr } +func (m *mockQuicListener) Addr() net.Addr { panic("not implemented") } -func (m *mockNetListener) Addr() net.Addr { - panic("not implemented") -} -var _ net.Listener = &mockNetListener{} var _ = Describe("Listener", func() { var ( - l *listener - netListener *mockNetListener - transport tpt.Transport + l *listener + quicListener *mockQuicListener + transport tpt.Transport ) BeforeEach(func() { - netListener = &mockNetListener{} + quicListener = &mockQuicListener{} transport = &QuicTransport{} l = &listener{ - quicListener: netListener, + quicListener: quicListener, transport: transport, } }) @@ -70,34 +64,13 @@ var _ = Describe("Listener", func() { It("closes", func() { err := l.Close() Expect(err).ToNot(HaveOccurred()) - Expect(netListener.closed).To(BeTrue()) + Expect(quicListener.closed).To(BeTrue()) }) Context("accepting", func() { - It("accepts a new conn", func() { - remoteAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:1234") - Expect(err).ToNot(HaveOccurred()) - localAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:4321") - Expect(err).ToNot(HaveOccurred()) - udpConn, err := net.DialUDP("udp", localAddr, remoteAddr) - netListener.connToAccept = udpConn - conn, err := l.Accept() - Expect(err).ToNot(HaveOccurred()) - Expect(conn.LocalMultiaddr().String()).To(Equal("/ip4/127.0.0.1/udp/4321")) - Expect(conn.RemoteMultiaddr().String()).To(Equal("/ip4/127.0.0.1/udp/1234")) - Expect(conn.Transport()).To(Equal(transport)) - }) - - It("errors if it can't read the muliaddresses of a conn", func() { - netListener.connToAccept = &net.UDPConn{} - _, err := l.Accept() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("nil multiaddr")) - }) - It("errors if no connection can be accepted", func() { testErr := errors.New("test error") - netListener.acceptErr = testErr + quicListener.acceptErr = testErr _, err := l.Accept() Expect(err).To(MatchError(testErr)) }) diff --git a/p2p/transport/quic/stream.go b/p2p/transport/quic/stream.go new file mode 100644 index 0000000000..c41b2b324b --- /dev/null +++ b/p2p/transport/quic/stream.go @@ -0,0 +1,29 @@ +package libp2pquic + +import ( + "time" + + smux "github.com/jbenet/go-stream-muxer" + quic "github.com/lucas-clemente/quic-go" +) + +// The quicStream is a very thin wrapper for a quic.Stream, adding some methods +// required to fulfill the smux.Stream interface +// TODO: this can be removed once the quic.Stream supports deadlines (quic-go#514) +type quicStream struct { + quic.Stream +} + +var _ smux.Stream = &quicStream{} + +func (s *quicStream) SetDeadline(time.Time) error { + return nil +} + +func (s *quicStream) SetReadDeadline(time.Time) error { + return nil +} + +func (s *quicStream) SetWriteDeadline(time.Time) error { + return nil +} diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 7505f94a6d..04f6a82751 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -18,6 +18,8 @@ type QuicTransport struct { dialers map[string]tpt.Dialer } +var _ tpt.Transport = &QuicTransport{} + // NewQuicTransport creates a new QUIC Transport // it tracks dialers and listeners created func NewQuicTransport() *QuicTransport { From 15cc2f7e686896c250e7fac24157fa90974dcb67 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 15 Jun 2017 16:33:02 +0200 Subject: [PATCH 013/138] open new streams synchronously --- p2p/transport/quic/conn.go | 4 +++- p2p/transport/quic/conn_test.go | 12 +++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index 1239e191e9..8d396e6c8d 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -52,8 +52,10 @@ func (c *quicConn) AcceptStream() (smux.Stream, error) { return &quicStream{Stream: str}, nil } +// OpenStream opens a new stream +// It blocks until a new stream can be opened (when limited by the QUIC maximum stream limit) func (c *quicConn) OpenStream() (smux.Stream, error) { - str, err := c.sess.OpenStream() + str, err := c.sess.OpenStreamSync() if err != nil { return nil, err } diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index bfce79c965..562c442eab 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -42,11 +42,13 @@ var _ quic.Session = &mockQuicSession{} func (s *mockQuicSession) AcceptStream() (quic.Stream, error) { return s.streamToAccept, s.streamAcceptErr } -func (s *mockQuicSession) OpenStream() (quic.Stream, error) { return s.streamToOpen, s.streamOpenErr } -func (s *mockQuicSession) OpenStreamSync() (quic.Stream, error) { return s.streamToOpen, nil } -func (s *mockQuicSession) Close(error) error { s.closed = true; return nil } -func (s *mockQuicSession) LocalAddr() net.Addr { return s.localAddr } -func (s *mockQuicSession) RemoteAddr() net.Addr { return s.remoteAddr } +func (s *mockQuicSession) OpenStream() (quic.Stream, error) { return s.streamToOpen, s.streamOpenErr } +func (s *mockQuicSession) OpenStreamSync() (quic.Stream, error) { + return s.streamToOpen, s.streamOpenErr +} +func (s *mockQuicSession) Close(error) error { s.closed = true; return nil } +func (s *mockQuicSession) LocalAddr() net.Addr { return s.localAddr } +func (s *mockQuicSession) RemoteAddr() net.Addr { return s.remoteAddr } var _ = Describe("Conn", func() { var ( From 1a9d35883b971f091837f1d87ab71fa9432d7940 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 15 Jun 2017 17:02:10 +0200 Subject: [PATCH 014/138] return resolved address in the listener --- p2p/transport/quic/listener.go | 6 +++++- p2p/transport/quic/listener_test.go | 15 +++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 18ab38201c..dbd644a0ff 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -34,9 +34,13 @@ func newListener(laddr ma.Multiaddr, t tpt.Transport) (*listener, error) { if err != nil { return nil, err } + addr, err := quicMultiAddress(qln.Addr()) + if err != nil { + return nil, err + } return &listener{ - laddr: laddr, + laddr: addr, quicListener: qln, transport: t, }, nil diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index 726c54f5d7..f4e03d63c8 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -27,8 +27,6 @@ func (m *mockQuicListener) Close() error { m.closed = true; ret func (m *mockQuicListener) Accept() (quic.Session, error) { return m.sessionToAccept, m.acceptErr } func (m *mockQuicListener) Addr() net.Addr { panic("not implemented") } - - var _ = Describe("Listener", func() { var ( l *listener @@ -46,19 +44,24 @@ var _ = Describe("Listener", func() { }) It("returns its addr", func() { - laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/12345/quic") + laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") Expect(err).ToNot(HaveOccurred()) l, err = newListener(laddr, nil) Expect(err).ToNot(HaveOccurred()) - Expect(l.Addr().String()).To(Equal("127.0.0.1:12345")) + as := l.Addr().String() + Expect(as).ToNot(Equal("127.0.0.1:0)")) + Expect(as).To(ContainSubstring("127.0.0.1:")) }) It("returns its multiaddr", func() { - laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/12346/quic") + laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") Expect(err).ToNot(HaveOccurred()) l, err = newListener(laddr, nil) Expect(err).ToNot(HaveOccurred()) - Expect(l.Multiaddr().String()).To(Equal("/ip4/127.0.0.1/udp/12346/quic")) + mas := l.Multiaddr().String() + Expect(mas).ToNot(Equal("/ip4/127.0.0.1/udp/0/quic")) + Expect(mas).To(ContainSubstring("/ip4/127.0.0.1/udp/")) + Expect(mas).To(ContainSubstring("/quic")) }) It("closes", func() { From c7bf01e46dd6c63b4171cf0b72a810167a27ef3a Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 16 Jun 2017 23:26:39 +0200 Subject: [PATCH 015/138] implement IsClosed() for the connection --- p2p/transport/quic/conn.go | 26 +++++++++++++++++++++----- p2p/transport/quic/conn_test.go | 15 ++++++++++++--- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index 8d396e6c8d..cbc6709f40 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -3,6 +3,7 @@ package libp2pquic import ( "fmt" "net" + "sync" smux "github.com/jbenet/go-stream-muxer" tpt "github.com/libp2p/go-libp2p-transport" @@ -12,11 +13,15 @@ import ( ) type quicConn struct { + mutex sync.RWMutex + sess quic.Session transport tpt.Transport laddr ma.Multiaddr raddr ma.Multiaddr + + closed bool } var _ tpt.Conn = &quicConn{} @@ -24,7 +29,6 @@ var _ tpt.MultiStreamConn = &quicConn{} func newQuicConn(sess quic.Session, t tpt.Transport) (*quicConn, error) { // analogues to manet.WrapNetConn - laddr, err := quicMultiAddress(sess.LocalAddr()) if err != nil { return nil, fmt.Errorf("failed to convert nconn.LocalAddr: %s", err) @@ -36,12 +40,16 @@ func newQuicConn(sess quic.Session, t tpt.Transport) (*quicConn, error) { return nil, fmt.Errorf("failed to convert nconn.RemoteAddr: %s", err) } - return &quicConn{ + c := &quicConn{ sess: sess, laddr: laddr, raddr: raddr, transport: t, - }, nil + } + + go c.watchClosed() + + return c, nil } func (c *quicConn) AcceptStream() (smux.Stream, error) { @@ -76,9 +84,17 @@ func (c *quicConn) Close() error { return c.sess.Close(nil) } -// TODO: implement this +func (c *quicConn) watchClosed() { + c.sess.WaitUntilClosed() + c.mutex.Lock() + c.closed = true + c.mutex.Unlock() +} + func (c *quicConn) IsClosed() bool { - return false + c.mutex.Lock() + defer c.mutex.Unlock() + return c.closed } func (c *quicConn) LocalAddr() net.Addr { diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 562c442eab..03e519d706 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -25,7 +25,8 @@ func (s *mockStream) StreamID() protocol.StreamID { return s.id } var _ quic.Stream = &mockStream{} type mockQuicSession struct { - closed bool + closed bool + waitUntilClosedChan chan struct{} // close this chan to make WaitUntilClosed return localAddr net.Addr remoteAddr net.Addr @@ -49,6 +50,7 @@ func (s *mockQuicSession) OpenStreamSync() (quic.Stream, error) { func (s *mockQuicSession) Close(error) error { s.closed = true; return nil } func (s *mockQuicSession) LocalAddr() net.Addr { return s.localAddr } func (s *mockQuicSession) RemoteAddr() net.Addr { return s.remoteAddr } +func (s *mockQuicSession) WaitUntilClosed() { <-s.waitUntilClosedChan } var _ = Describe("Conn", func() { var ( @@ -58,8 +60,9 @@ var _ = Describe("Conn", func() { BeforeEach(func() { sess = &mockQuicSession{ - localAddr: &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1337}, - remoteAddr: &net.UDPAddr{IP: net.IPv4(192, 168, 13, 37), Port: 1234}, + localAddr: &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1337}, + remoteAddr: &net.UDPAddr{IP: net.IPv4(192, 168, 13, 37), Port: 1234}, + waitUntilClosedChan: make(chan struct{}), } var err error conn, err = newQuicConn(sess, nil) @@ -82,6 +85,12 @@ var _ = Describe("Conn", func() { Expect(sess.closed).To(BeTrue()) }) + It("says if it is closed", func() { + Consistently(func() bool { return conn.IsClosed() }).Should(BeFalse()) + close(sess.waitUntilClosedChan) + Eventually(func() bool { return conn.IsClosed() }).Should(BeTrue()) + }) + Context("opening streams", func() { It("opens streams", func() { s := &mockStream{id: 1337} From 554bd36fb626e89a3a515851634ff5c468be1b79 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 14 Jul 2017 16:08:35 +0700 Subject: [PATCH 016/138] use the new quic-go API The tls.Config was removed from the quic.Config in https://github.com/lucas-clemente/quic-go/pull/713. --- p2p/transport/quic/dialer.go | 3 +-- p2p/transport/quic/listener.go | 10 +++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/p2p/transport/quic/dialer.go b/p2p/transport/quic/dialer.go index 8563674369..fd47186b2a 100644 --- a/p2p/transport/quic/dialer.go +++ b/p2p/transport/quic/dialer.go @@ -25,13 +25,12 @@ func newDialer(transport tpt.Transport) (*dialer, error) { func (d *dialer) Dial(raddr ma.Multiaddr) (tpt.Conn, error) { // TODO: check that raddr is a QUIC address - tlsConf := &tls.Config{InsecureSkipVerify: true} _, host, err := manet.DialArgs(raddr) if err != nil { return nil, err } - qsess, err := quic.DialAddr(host, &quic.Config{TLSConfig: tlsConf}) + qsess, err := quic.DialAddr(host, &tls.Config{InsecureSkipVerify: true}, nil) if err != nil { return nil, err } diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index dbd644a0ff..6ef1427378 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -20,17 +20,13 @@ type listener struct { var _ tpt.Listener = &listener{} func newListener(laddr ma.Multiaddr, t tpt.Transport) (*listener, error) { - qconf := &quic.Config{ - // we need to provide a certificate here - // use the demo certificate from quic-go - TLSConfig: testdata.GetTLSConfig(), - } - _, host, err := manet.DialArgs(laddr) if err != nil { return nil, err } - qln, err := quic.ListenAddr(host, qconf) + // we need to provide a certificate here + // use the demo certificate from quic-go + qln, err := quic.ListenAddr(host, testdata.GetTLSConfig(), nil) if err != nil { return nil, err } From 7c6f9c07cfedec1056eeac677d1efeea72c4aacf Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 16 Jul 2017 12:24:43 +0700 Subject: [PATCH 017/138] use the new quic-go stream deadline Support for stream deadlines was added in https://github.com/lucas-clemente/quic-go/pull/579. --- p2p/transport/quic/conn.go | 13 ++----------- p2p/transport/quic/conn_test.go | 20 ++++++++++++-------- p2p/transport/quic/stream.go | 29 ----------------------------- 3 files changed, 14 insertions(+), 48 deletions(-) delete mode 100644 p2p/transport/quic/stream.go diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index cbc6709f40..511fa89a36 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -46,28 +46,19 @@ func newQuicConn(sess quic.Session, t tpt.Transport) (*quicConn, error) { raddr: raddr, transport: t, } - go c.watchClosed() return c, nil } func (c *quicConn) AcceptStream() (smux.Stream, error) { - str, err := c.sess.AcceptStream() - if err != nil { - return nil, err - } - return &quicStream{Stream: str}, nil + return c.sess.AcceptStream() } // OpenStream opens a new stream // It blocks until a new stream can be opened (when limited by the QUIC maximum stream limit) func (c *quicConn) OpenStream() (smux.Stream, error) { - str, err := c.sess.OpenStreamSync() - if err != nil { - return nil, err - } - return &quicStream{Stream: str}, nil + return c.sess.OpenStreamSync() } func (c *quicConn) Serve(handler smux.StreamHandler) { diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 03e519d706..7c80681736 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -3,6 +3,7 @@ package libp2pquic import ( "errors" "net" + "time" smux "github.com/jbenet/go-stream-muxer" quic "github.com/lucas-clemente/quic-go" @@ -16,11 +17,14 @@ type mockStream struct { id protocol.StreamID } -func (s *mockStream) Close() error { return nil } -func (s *mockStream) Reset(error) { return } -func (s *mockStream) Read([]byte) (int, error) { return 0, nil } -func (s *mockStream) Write([]byte) (int, error) { return 0, nil } -func (s *mockStream) StreamID() protocol.StreamID { return s.id } +func (s *mockStream) Close() error { return nil } +func (s *mockStream) Reset(error) { return } +func (s *mockStream) Read([]byte) (int, error) { return 0, nil } +func (s *mockStream) Write([]byte) (int, error) { return 0, nil } +func (s *mockStream) StreamID() protocol.StreamID { return s.id } +func (s *mockStream) SetReadDeadline(time.Time) error { panic("not implemented") } +func (s *mockStream) SetWriteDeadline(time.Time) error { panic("not implemented") } +func (s *mockStream) SetDeadline(time.Time) error { panic("not implemented") } var _ quic.Stream = &mockStream{} @@ -97,7 +101,7 @@ var _ = Describe("Conn", func() { sess.streamToOpen = s str, err := conn.OpenStream() Expect(err).ToNot(HaveOccurred()) - Expect(str.(*quicStream).Stream).To(Equal(s)) + Expect(str).To(Equal(s)) }) It("errors when it can't open a stream", func() { @@ -114,7 +118,7 @@ var _ = Describe("Conn", func() { sess.streamToAccept = s str, err := conn.AcceptStream() Expect(err).ToNot(HaveOccurred()) - Expect(str.(*quicStream).Stream).To(Equal(s)) + Expect(str).To(Equal(s)) }) It("errors when it can't open a stream", func() { @@ -150,7 +154,7 @@ var _ = Describe("Conn", func() { returned = true }() Eventually(func() bool { return handlerCalled }).Should(BeTrue()) - Expect(handlerCalledWith.(*quicStream).Stream).To(Equal(str)) + Expect(handlerCalledWith).To(Equal(str)) // make the go-routine return sess.streamAcceptErr = errors.New("stop test") }) diff --git a/p2p/transport/quic/stream.go b/p2p/transport/quic/stream.go deleted file mode 100644 index c41b2b324b..0000000000 --- a/p2p/transport/quic/stream.go +++ /dev/null @@ -1,29 +0,0 @@ -package libp2pquic - -import ( - "time" - - smux "github.com/jbenet/go-stream-muxer" - quic "github.com/lucas-clemente/quic-go" -) - -// The quicStream is a very thin wrapper for a quic.Stream, adding some methods -// required to fulfill the smux.Stream interface -// TODO: this can be removed once the quic.Stream supports deadlines (quic-go#514) -type quicStream struct { - quic.Stream -} - -var _ smux.Stream = &quicStream{} - -func (s *quicStream) SetDeadline(time.Time) error { - return nil -} - -func (s *quicStream) SetReadDeadline(time.Time) error { - return nil -} - -func (s *quicStream) SetWriteDeadline(time.Time) error { - return nil -} From fd916f70a701add4b3a39c0e3ea1fc95e604c85d Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 19 Jul 2017 10:03:47 +0700 Subject: [PATCH 018/138] import the correct go-stream-muxer repo --- p2p/transport/quic/conn.go | 2 +- p2p/transport/quic/conn_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index 511fa89a36..3b0310fca3 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -5,8 +5,8 @@ import ( "net" "sync" - smux "github.com/jbenet/go-stream-muxer" tpt "github.com/libp2p/go-libp2p-transport" + smux "github.com/libp2p/go-stream-muxer" quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 7c80681736..62288cbc86 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -5,7 +5,7 @@ import ( "net" "time" - smux "github.com/jbenet/go-stream-muxer" + smux "github.com/libp2p/go-stream-muxer" quic "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/protocol" From 214392a00d418eb402d2ec411d77fb5cea007167 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 8 Aug 2017 11:00:42 +0700 Subject: [PATCH 019/138] implement the new stream reset recently added to smux.Stream It was added in https://github.com/libp2p/go-stream-muxer/commit/be2992bb9 --- p2p/transport/quic/conn.go | 6 ++++-- p2p/transport/quic/conn_test.go | 6 +++--- p2p/transport/quic/stream.go | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 p2p/transport/quic/stream.go diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index 3b0310fca3..1805334870 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -52,13 +52,15 @@ func newQuicConn(sess quic.Session, t tpt.Transport) (*quicConn, error) { } func (c *quicConn) AcceptStream() (smux.Stream, error) { - return c.sess.AcceptStream() + str, err := c.sess.AcceptStream() + return &stream{str}, err } // OpenStream opens a new stream // It blocks until a new stream can be opened (when limited by the QUIC maximum stream limit) func (c *quicConn) OpenStream() (smux.Stream, error) { - return c.sess.OpenStreamSync() + str, err := c.sess.OpenStreamSync() + return &stream{str}, err } func (c *quicConn) Serve(handler smux.StreamHandler) { diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 62288cbc86..8ef5755ae6 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -101,7 +101,7 @@ var _ = Describe("Conn", func() { sess.streamToOpen = s str, err := conn.OpenStream() Expect(err).ToNot(HaveOccurred()) - Expect(str).To(Equal(s)) + Expect(str.(*stream).Stream).To(Equal(s)) }) It("errors when it can't open a stream", func() { @@ -118,7 +118,7 @@ var _ = Describe("Conn", func() { sess.streamToAccept = s str, err := conn.AcceptStream() Expect(err).ToNot(HaveOccurred()) - Expect(str).To(Equal(s)) + Expect(str.(*stream).Stream).To(Equal(s)) }) It("errors when it can't open a stream", func() { @@ -154,7 +154,7 @@ var _ = Describe("Conn", func() { returned = true }() Eventually(func() bool { return handlerCalled }).Should(BeTrue()) - Expect(handlerCalledWith).To(Equal(str)) + Expect(handlerCalledWith.(*stream).Stream).To(Equal(str)) // make the go-routine return sess.streamAcceptErr = errors.New("stop test") }) diff --git a/p2p/transport/quic/stream.go b/p2p/transport/quic/stream.go new file mode 100644 index 0000000000..5d5ad242cf --- /dev/null +++ b/p2p/transport/quic/stream.go @@ -0,0 +1,17 @@ +package libp2pquic + +import ( + smux "github.com/libp2p/go-stream-muxer" + "github.com/lucas-clemente/quic-go" +) + +type stream struct { + quic.Stream +} + +var _ smux.Stream = &stream{} + +func (s *stream) Reset() error { + s.Stream.Reset(nil) + return nil +} From 2f4cb702e37d1b11ed1117dd780cb24d2019e754 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 8 Aug 2017 11:07:24 +0700 Subject: [PATCH 020/138] use the new quic-go session context The session context was introduced in https://github.com/lucas-clemente/quic-go/pull/774. --- p2p/transport/quic/conn.go | 2 +- p2p/transport/quic/conn_test.go | 29 +++++++++++++++++------------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index 1805334870..3f8ebe91a7 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -78,7 +78,7 @@ func (c *quicConn) Close() error { } func (c *quicConn) watchClosed() { - c.sess.WaitUntilClosed() + <-c.sess.Context().Done() c.mutex.Lock() c.closed = true c.mutex.Unlock() diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 8ef5755ae6..4a38d222a8 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -1,6 +1,7 @@ package libp2pquic import ( + "context" "errors" "net" "time" @@ -25,12 +26,13 @@ func (s *mockStream) StreamID() protocol.StreamID { return s.id } func (s *mockStream) SetReadDeadline(time.Time) error { panic("not implemented") } func (s *mockStream) SetWriteDeadline(time.Time) error { panic("not implemented") } func (s *mockStream) SetDeadline(time.Time) error { panic("not implemented") } +func (s *mockStream) Context() context.Context { panic("not implemented") } var _ quic.Stream = &mockStream{} type mockQuicSession struct { - closed bool - waitUntilClosedChan chan struct{} // close this chan to make WaitUntilClosed return + closed bool + context context.Context localAddr net.Addr remoteAddr net.Addr @@ -51,22 +53,25 @@ func (s *mockQuicSession) OpenStream() (quic.Stream, error) { return s.streamToO func (s *mockQuicSession) OpenStreamSync() (quic.Stream, error) { return s.streamToOpen, s.streamOpenErr } -func (s *mockQuicSession) Close(error) error { s.closed = true; return nil } -func (s *mockQuicSession) LocalAddr() net.Addr { return s.localAddr } -func (s *mockQuicSession) RemoteAddr() net.Addr { return s.remoteAddr } -func (s *mockQuicSession) WaitUntilClosed() { <-s.waitUntilClosedChan } +func (s *mockQuicSession) Close(error) error { s.closed = true; return nil } +func (s *mockQuicSession) LocalAddr() net.Addr { return s.localAddr } +func (s *mockQuicSession) RemoteAddr() net.Addr { return s.remoteAddr } +func (s *mockQuicSession) Context() context.Context { return s.context } var _ = Describe("Conn", func() { var ( - conn *quicConn - sess *mockQuicSession + conn *quicConn + sess *mockQuicSession + ctxCancel context.CancelFunc ) BeforeEach(func() { + var ctx context.Context + ctx, ctxCancel = context.WithCancel(context.Background()) sess = &mockQuicSession{ - localAddr: &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1337}, - remoteAddr: &net.UDPAddr{IP: net.IPv4(192, 168, 13, 37), Port: 1234}, - waitUntilClosedChan: make(chan struct{}), + localAddr: &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1337}, + remoteAddr: &net.UDPAddr{IP: net.IPv4(192, 168, 13, 37), Port: 1234}, + context: ctx, } var err error conn, err = newQuicConn(sess, nil) @@ -91,7 +96,7 @@ var _ = Describe("Conn", func() { It("says if it is closed", func() { Consistently(func() bool { return conn.IsClosed() }).Should(BeFalse()) - close(sess.waitUntilClosedChan) + ctxCancel() Eventually(func() bool { return conn.IsClosed() }).Should(BeTrue()) }) From e61b4ddf16110864728d2421b64bb8a5134c59a4 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 8 Aug 2017 11:19:37 +0700 Subject: [PATCH 021/138] remove Serve from the quicConn --- p2p/transport/quic/conn.go | 10 -------- p2p/transport/quic/conn_test.go | 44 --------------------------------- 2 files changed, 54 deletions(-) diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index 3f8ebe91a7..4881f99315 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -63,16 +63,6 @@ func (c *quicConn) OpenStream() (smux.Stream, error) { return &stream{str}, err } -func (c *quicConn) Serve(handler smux.StreamHandler) { - for { // accept loop - s, err := c.AcceptStream() - if err != nil { - return // err always means closed. - } - go handler(s) - } -} - func (c *quicConn) Close() error { return c.sess.Close(nil) } diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 4a38d222a8..774df1d2ce 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -6,7 +6,6 @@ import ( "net" "time" - smux "github.com/libp2p/go-stream-muxer" quic "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/protocol" @@ -133,47 +132,4 @@ var _ = Describe("Conn", func() { Expect(err).To(MatchError(testErr)) }) }) - - Context("serving", func() { - var ( - handler func(smux.Stream) - handlerCalled bool - handlerCalledWith smux.Stream - ) - - BeforeEach(func() { - handlerCalled = false - handlerCalledWith = nil - handler = func(s smux.Stream) { - handlerCalledWith = s - handlerCalled = true - } - }) - - It("calls the handler", func() { - str := &mockStream{id: 5} - sess.streamToAccept = str - var returned bool - go func() { - conn.Serve(handler) - returned = true - }() - Eventually(func() bool { return handlerCalled }).Should(BeTrue()) - Expect(handlerCalledWith.(*stream).Stream).To(Equal(str)) - // make the go-routine return - sess.streamAcceptErr = errors.New("stop test") - }) - - It("returns when accepting a stream errors", func() { - sess.streamAcceptErr = errors.New("accept err") - var returned bool - go func() { - conn.Serve(handler) - returned = true - }() - Eventually(func() bool { return returned }).Should(BeTrue()) - Expect(handlerCalled).To(BeFalse()) - }) - }) - }) From 969c12de2213743056a975f156ceb0669b045626 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 28 Aug 2017 18:06:33 +0700 Subject: [PATCH 022/138] generate a private key and a self-signed certificate for the listener --- p2p/transport/quic/listener.go | 36 ++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 6ef1427378..b0d61c6966 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -1,11 +1,16 @@ package libp2pquic import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "math/big" "net" tpt "github.com/libp2p/go-libp2p-transport" quic "github.com/lucas-clemente/quic-go" - testdata "github.com/lucas-clemente/quic-go/testdata" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" ) @@ -24,9 +29,11 @@ func newListener(laddr ma.Multiaddr, t tpt.Transport) (*listener, error) { if err != nil { return nil, err } - // we need to provide a certificate here - // use the demo certificate from quic-go - qln, err := quic.ListenAddr(host, testdata.GetTLSConfig(), nil) + tlsConf, err := generateTLSConfig() + if err != nil { + return nil, err + } + qln, err := quic.ListenAddr(host, tlsConf, nil) if err != nil { return nil, err } @@ -61,3 +68,24 @@ func (l *listener) Addr() net.Addr { func (l *listener) Multiaddr() ma.Multiaddr { return l.laddr } + +// Generate a bare-bones TLS config for the server. +// The client doesn't verify the certificate yet. +func generateTLSConfig() (*tls.Config, error) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + template := x509.Certificate{SerialNumber: big.NewInt(1)} + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) + if err != nil { + return nil, err + } + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + return nil, err + } + return &tls.Config{Certificates: []tls.Certificate{tlsCert}}, nil +} From 36115aef16dfef70c6a960fc1e09a11042f23e93 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 1 Sep 2017 12:45:06 +0700 Subject: [PATCH 023/138] use the new exported types in the quic-go API This also requires dropping support for Go 1.8. --- p2p/transport/quic/conn_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 774df1d2ce..815e269a0e 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -7,21 +7,20 @@ import ( "time" quic "github.com/lucas-clemente/quic-go" - "github.com/lucas-clemente/quic-go/protocol" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) type mockStream struct { - id protocol.StreamID + id quic.StreamID } func (s *mockStream) Close() error { return nil } func (s *mockStream) Reset(error) { return } func (s *mockStream) Read([]byte) (int, error) { return 0, nil } func (s *mockStream) Write([]byte) (int, error) { return 0, nil } -func (s *mockStream) StreamID() protocol.StreamID { return s.id } +func (s *mockStream) StreamID() quic.StreamID { return s.id } func (s *mockStream) SetReadDeadline(time.Time) error { panic("not implemented") } func (s *mockStream) SetWriteDeadline(time.Time) error { panic("not implemented") } func (s *mockStream) SetDeadline(time.Time) error { panic("not implemented") } From 443d2a836a3965b6607810fdcc1fd546658cd9c9 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 6 Sep 2017 12:56:04 +0200 Subject: [PATCH 024/138] use the renamed transport interfaces --- p2p/transport/quic/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index 4881f99315..1cc72686ed 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -25,7 +25,7 @@ type quicConn struct { } var _ tpt.Conn = &quicConn{} -var _ tpt.MultiStreamConn = &quicConn{} +var _ tpt.MultiplexConn = &quicConn{} func newQuicConn(sess quic.Session, t tpt.Transport) (*quicConn, error) { // analogues to manet.WrapNetConn From 5eaaf4c892a85fbfba11bf4d72a52b8a322bfdb8 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 28 Jan 2018 14:34:18 +0700 Subject: [PATCH 025/138] refactor for transport interface changes --- p2p/transport/quic/conn.go | 104 +++++++--------- p2p/transport/quic/conn_test.go | 179 +++++++++++---------------- p2p/transport/quic/crypto.go | 126 +++++++++++++++++++ p2p/transport/quic/dialer.go | 48 ------- p2p/transport/quic/dialer_test.go | 52 -------- p2p/transport/quic/listener.go | 89 +++++++------ p2p/transport/quic/listener_test.go | 92 ++++++-------- p2p/transport/quic/stream.go | 8 +- p2p/transport/quic/transport.go | 120 ++++++++++-------- p2p/transport/quic/transport_test.go | 74 ++--------- 10 files changed, 410 insertions(+), 482 deletions(-) create mode 100644 p2p/transport/quic/crypto.go delete mode 100644 p2p/transport/quic/dialer.go delete mode 100644 p2p/transport/quic/dialer_test.go diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index 1cc72686ed..e5ccdc53c8 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -1,10 +1,10 @@ package libp2pquic import ( - "fmt" "net" - "sync" + ic "github.com/libp2p/go-libp2p-crypto" + peer "github.com/libp2p/go-libp2p-peer" tpt "github.com/libp2p/go-libp2p-transport" smux "github.com/libp2p/go-stream-muxer" quic "github.com/lucas-clemente/quic-go" @@ -12,96 +12,78 @@ import ( manet "github.com/multiformats/go-multiaddr-net" ) -type quicConn struct { - mutex sync.RWMutex - +type conn struct { sess quic.Session transport tpt.Transport - laddr ma.Multiaddr - raddr ma.Multiaddr + localPeer peer.ID + privKey ic.PrivKey + localMultiaddr ma.Multiaddr - closed bool + remotePeerID peer.ID + remotePubKey ic.PubKey + remoteMultiaddr ma.Multiaddr } -var _ tpt.Conn = &quicConn{} -var _ tpt.MultiplexConn = &quicConn{} - -func newQuicConn(sess quic.Session, t tpt.Transport) (*quicConn, error) { - // analogues to manet.WrapNetConn - laddr, err := quicMultiAddress(sess.LocalAddr()) - if err != nil { - return nil, fmt.Errorf("failed to convert nconn.LocalAddr: %s", err) - } +var _ tpt.Conn = &conn{} - // analogues to manet.WrapNetConn - raddr, err := quicMultiAddress(sess.RemoteAddr()) - if err != nil { - return nil, fmt.Errorf("failed to convert nconn.RemoteAddr: %s", err) - } - - c := &quicConn{ - sess: sess, - laddr: laddr, - raddr: raddr, - transport: t, - } - go c.watchClosed() - - return c, nil +func (c *conn) Close() error { + return c.sess.Close(nil) } -func (c *quicConn) AcceptStream() (smux.Stream, error) { - str, err := c.sess.AcceptStream() - return &stream{str}, err +// IsClosed returns whether a connection is fully closed. +func (c *conn) IsClosed() bool { + return c.sess.Context().Err() != nil } -// OpenStream opens a new stream -// It blocks until a new stream can be opened (when limited by the QUIC maximum stream limit) -func (c *quicConn) OpenStream() (smux.Stream, error) { - str, err := c.sess.OpenStreamSync() - return &stream{str}, err +// OpenStream creates a new stream. +func (c *conn) OpenStream() (smux.Stream, error) { + qstr, err := c.sess.OpenStreamSync() + return &stream{Stream: qstr}, err } -func (c *quicConn) Close() error { - return c.sess.Close(nil) +// AcceptStream accepts a stream opened by the other side. +func (c *conn) AcceptStream() (smux.Stream, error) { + qstr, err := c.sess.AcceptStream() + return &stream{Stream: qstr}, err } -func (c *quicConn) watchClosed() { - <-c.sess.Context().Done() - c.mutex.Lock() - c.closed = true - c.mutex.Unlock() +// LocalPeer returns our peer ID +func (c *conn) LocalPeer() peer.ID { + return c.localPeer } -func (c *quicConn) IsClosed() bool { - c.mutex.Lock() - defer c.mutex.Unlock() - return c.closed +// LocalPrivateKey returns our private key +func (c *conn) LocalPrivateKey() ic.PrivKey { + return c.privKey } -func (c *quicConn) LocalAddr() net.Addr { - return c.sess.LocalAddr() +// RemotePeer returns the peer ID of the remote peer. +func (c *conn) RemotePeer() peer.ID { + return c.remotePeerID } -func (c *quicConn) LocalMultiaddr() ma.Multiaddr { - return c.laddr +// RemotePublicKey returns the public key of the remote peer. +func (c *conn) RemotePublicKey() ic.PubKey { + return c.remotePubKey } -func (c *quicConn) RemoteAddr() net.Addr { - return c.sess.RemoteAddr() +// LocalMultiaddr returns the local Multiaddr associated +func (c *conn) LocalMultiaddr() ma.Multiaddr { + return c.localMultiaddr } -func (c *quicConn) RemoteMultiaddr() ma.Multiaddr { - return c.raddr +// RemoteMultiaddr returns the remote Multiaddr associated +func (c *conn) RemoteMultiaddr() ma.Multiaddr { + return c.remoteMultiaddr } -func (c *quicConn) Transport() tpt.Transport { +func (c *conn) Transport() tpt.Transport { return c.transport } // TODO: there must be a better way to do this -func quicMultiAddress(na net.Addr) (ma.Multiaddr, error) { +func quicMultiaddr(na net.Addr) (ma.Multiaddr, error) { udpMA, err := manet.FromNetAddr(na) if err != nil { return nil, err diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 815e269a0e..473a50c6e9 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -2,133 +2,92 @@ package libp2pquic import ( "context" - "errors" - "net" - "time" + "crypto/rand" + "crypto/rsa" + "crypto/x509" - quic "github.com/lucas-clemente/quic-go" + ic "github.com/libp2p/go-libp2p-crypto" + peer "github.com/libp2p/go-libp2p-peer" + tpt "github.com/libp2p/go-libp2p-transport" + ma "github.com/multiformats/go-multiaddr" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) -type mockStream struct { - id quic.StreamID -} - -func (s *mockStream) Close() error { return nil } -func (s *mockStream) Reset(error) { return } -func (s *mockStream) Read([]byte) (int, error) { return 0, nil } -func (s *mockStream) Write([]byte) (int, error) { return 0, nil } -func (s *mockStream) StreamID() quic.StreamID { return s.id } -func (s *mockStream) SetReadDeadline(time.Time) error { panic("not implemented") } -func (s *mockStream) SetWriteDeadline(time.Time) error { panic("not implemented") } -func (s *mockStream) SetDeadline(time.Time) error { panic("not implemented") } -func (s *mockStream) Context() context.Context { panic("not implemented") } - -var _ quic.Stream = &mockStream{} - -type mockQuicSession struct { - closed bool - context context.Context - - localAddr net.Addr - remoteAddr net.Addr - - streamToAccept quic.Stream - streamAcceptErr error - - streamToOpen quic.Stream - streamOpenErr error -} - -var _ quic.Session = &mockQuicSession{} - -func (s *mockQuicSession) AcceptStream() (quic.Stream, error) { - return s.streamToAccept, s.streamAcceptErr -} -func (s *mockQuicSession) OpenStream() (quic.Stream, error) { return s.streamToOpen, s.streamOpenErr } -func (s *mockQuicSession) OpenStreamSync() (quic.Stream, error) { - return s.streamToOpen, s.streamOpenErr -} -func (s *mockQuicSession) Close(error) error { s.closed = true; return nil } -func (s *mockQuicSession) LocalAddr() net.Addr { return s.localAddr } -func (s *mockQuicSession) RemoteAddr() net.Addr { return s.remoteAddr } -func (s *mockQuicSession) Context() context.Context { return s.context } - -var _ = Describe("Conn", func() { +var _ = Describe("Connection", func() { var ( - conn *quicConn - sess *mockQuicSession - ctxCancel context.CancelFunc + serverKey, clientKey ic.PrivKey + serverID, clientID peer.ID ) - BeforeEach(func() { - var ctx context.Context - ctx, ctxCancel = context.WithCancel(context.Background()) - sess = &mockQuicSession{ - localAddr: &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1337}, - remoteAddr: &net.UDPAddr{IP: net.IPv4(192, 168, 13, 37), Port: 1234}, - context: ctx, - } - var err error - conn, err = newQuicConn(sess, nil) + createPeer := func() ic.PrivKey { + key, err := rsa.GenerateKey(rand.Reader, 1024) Expect(err).ToNot(HaveOccurred()) - }) - - It("has the correct local address", func() { - Expect(conn.LocalAddr()).To(Equal(sess.localAddr)) - Expect(conn.LocalMultiaddr().String()).To(Equal("/ip4/127.0.0.1/udp/1337/quic")) - }) - - It("has the correct remote address", func() { - Expect(conn.RemoteAddr()).To(Equal(sess.remoteAddr)) - Expect(conn.RemoteMultiaddr().String()).To(Equal("/ip4/192.168.13.37/udp/1234/quic")) - }) + priv, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(key)) + Expect(err).ToNot(HaveOccurred()) + return priv + } - It("closes", func() { - err := conn.Close() + runServer := func() (<-chan ma.Multiaddr, <-chan tpt.Conn) { + serverTransport, err := NewTransport(serverKey) Expect(err).ToNot(HaveOccurred()) - Expect(sess.closed).To(BeTrue()) - }) + addrChan := make(chan ma.Multiaddr) + connChan := make(chan tpt.Conn) + go func() { + defer GinkgoRecover() + addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + Expect(err).ToNot(HaveOccurred()) + ln, err := serverTransport.Listen(addr) + Expect(err).ToNot(HaveOccurred()) + addrChan <- ln.Multiaddr() + conn, err := ln.Accept() + Expect(err).ToNot(HaveOccurred()) + connChan <- conn + }() + return addrChan, connChan + } - It("says if it is closed", func() { - Consistently(func() bool { return conn.IsClosed() }).Should(BeFalse()) - ctxCancel() - Eventually(func() bool { return conn.IsClosed() }).Should(BeTrue()) + BeforeEach(func() { + var err error + serverKey = createPeer() + serverID, err = peer.IDFromPrivateKey(serverKey) + Expect(err).ToNot(HaveOccurred()) + clientKey = createPeer() + clientID, err = peer.IDFromPrivateKey(clientKey) + Expect(err).ToNot(HaveOccurred()) }) - Context("opening streams", func() { - It("opens streams", func() { - s := &mockStream{id: 1337} - sess.streamToOpen = s - str, err := conn.OpenStream() - Expect(err).ToNot(HaveOccurred()) - Expect(str.(*stream).Stream).To(Equal(s)) - }) - - It("errors when it can't open a stream", func() { - testErr := errors.New("stream open err") - sess.streamOpenErr = testErr - _, err := conn.OpenStream() - Expect(err).To(MatchError(testErr)) - }) + It("handshakes", func() { + serverAddrChan, serverConnChan := runServer() + clientTransport, err := NewTransport(clientKey) + Expect(err).ToNot(HaveOccurred()) + serverAddr := <-serverAddrChan + conn, err := clientTransport.Dial(context.Background(), serverAddr, serverID) + Expect(err).ToNot(HaveOccurred()) + serverConn := <-serverConnChan + Expect(conn.LocalPeer()).To(Equal(clientID)) + Expect(conn.LocalPrivateKey()).To(Equal(clientKey)) + Expect(conn.RemotePeer()).To(Equal(serverID)) + Expect(conn.RemotePublicKey()).To(Equal(serverKey.GetPublic())) + Expect(serverConn.LocalPeer()).To(Equal(serverID)) + Expect(serverConn.LocalPrivateKey()).To(Equal(serverKey)) + Expect(serverConn.RemotePeer()).To(Equal(clientID)) + Expect(serverConn.RemotePublicKey()).To(Equal(clientKey.GetPublic())) }) - Context("accepting streams", func() { - It("accepts streams", func() { - s := &mockStream{id: 1337} - sess.streamToAccept = s - str, err := conn.AcceptStream() - Expect(err).ToNot(HaveOccurred()) - Expect(str.(*stream).Stream).To(Equal(s)) - }) + It("fails if the peer ID doesn't match", func() { + thirdPartyID, err := peer.IDFromPrivateKey(createPeer()) + Expect(err).ToNot(HaveOccurred()) - It("errors when it can't open a stream", func() { - testErr := errors.New("stream open err") - sess.streamAcceptErr = testErr - _, err := conn.AcceptStream() - Expect(err).To(MatchError(testErr)) - }) + serverAddrChan, _ := runServer() + clientTransport, err := NewTransport(clientKey) + Expect(err).ToNot(HaveOccurred()) + serverAddr := <-serverAddrChan + // dial, but expect the wrong peer ID + _, err = clientTransport.Dial(context.Background(), serverAddr, thirdPartyID) + Expect(err).To(MatchError("peer IDs don't match")) + // TODO(#2): don't accept a connection if the client's peer verification fails + // Consistently(serverConnChan).ShouldNot(Receive()) }) }) diff --git a/p2p/transport/quic/crypto.go b/p2p/transport/quic/crypto.go new file mode 100644 index 0000000000..a6dee953d7 --- /dev/null +++ b/p2p/transport/quic/crypto.go @@ -0,0 +1,126 @@ +package libp2pquic + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "errors" + "math/big" + "time" + + "github.com/lucas-clemente/quic-go" + + "github.com/gogo/protobuf/proto" + ic "github.com/libp2p/go-libp2p-crypto" + pb "github.com/libp2p/go-libp2p-crypto/pb" +) + +// mint certificate selection is broken. +const hostname = "quic.ipfs" + +type connectionStater interface { + ConnectionState() quic.ConnectionState +} + +// TODO: make this private +func GenerateConfig(privKey ic.PrivKey) (*tls.Config, error) { + key, hostCert, err := keyToCertificate(privKey) + if err != nil { + return nil, err + } + // The ephemeral key used just for a couple of connections (or a limited time). + ephemeralKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + // Sign the ephemeral key using the host key. + // This is the only time that the host's private key of the peer is needed. + // Note that this step could be done asynchronously, such that a running node doesn't need access its private key at all. + certTemplate := &x509.Certificate{ + DNSNames: []string{hostname}, + SerialNumber: big.NewInt(1), + NotBefore: time.Now().Add(-24 * time.Hour), + NotAfter: time.Now().Add(30 * 24 * time.Hour), + } + certDER, err := x509.CreateCertificate(rand.Reader, certTemplate, hostCert, ephemeralKey.Public(), key) + if err != nil { + return nil, err + } + cert, err := x509.ParseCertificate(certDER) + if err != nil { + return nil, err + } + return &tls.Config{ + ServerName: hostname, + InsecureSkipVerify: true, // This is not insecure here. We will verify the cert chain ourselves. + ClientAuth: tls.RequireAnyClientCert, + Certificates: []tls.Certificate{{ + Certificate: [][]byte{cert.Raw, hostCert.Raw}, + PrivateKey: ephemeralKey, + }}, + }, nil +} + +func getRemotePubKey(conn connectionStater) (ic.PubKey, error) { + certChain := conn.ConnectionState().PeerCertificates + if len(certChain) != 2 { + return nil, errors.New("expected 2 certificates in the chain") + } + pool := x509.NewCertPool() + pool.AddCert(certChain[1]) + if _, err := certChain[0].Verify(x509.VerifyOptions{Roots: pool}); err != nil { + return nil, err + } + remotePubKey, err := x509.MarshalPKIXPublicKey(certChain[1].PublicKey) + if err != nil { + return nil, err + } + return ic.UnmarshalRsaPublicKey(remotePubKey) +} + +func keyToCertificate(sk ic.PrivKey) (interface{}, *x509.Certificate, error) { + sn, err := rand.Int(rand.Reader, big.NewInt(1<<62)) + if err != nil { + return nil, nil, err + } + tmpl := &x509.Certificate{ + SerialNumber: sn, + NotBefore: time.Now().Add(-24 * time.Hour), + NotAfter: time.Now().Add(30 * 24 * time.Hour), + IsCA: true, + BasicConstraintsValid: true, + } + + var publicKey, privateKey interface{} + keyBytes, err := sk.Bytes() + if err != nil { + return nil, nil, err + } + pbmes := new(pb.PrivateKey) + if err := proto.Unmarshal(keyBytes, pbmes); err != nil { + return nil, nil, err + } + switch pbmes.GetType() { + case pb.KeyType_RSA: + k, err := x509.ParsePKCS1PrivateKey(pbmes.GetData()) + if err != nil { + return nil, nil, err + } + publicKey = &k.PublicKey + privateKey = k + // TODO: add support for ECDSA + default: + return nil, nil, errors.New("unsupported key type for TLS") + } + certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, publicKey, privateKey) + if err != nil { + return nil, nil, err + } + cert, err := x509.ParseCertificate(certDER) + if err != nil { + return nil, nil, err + } + return privateKey, cert, nil +} diff --git a/p2p/transport/quic/dialer.go b/p2p/transport/quic/dialer.go deleted file mode 100644 index fd47186b2a..0000000000 --- a/p2p/transport/quic/dialer.go +++ /dev/null @@ -1,48 +0,0 @@ -package libp2pquic - -import ( - "context" - "crypto/tls" - - tpt "github.com/libp2p/go-libp2p-transport" - quic "github.com/lucas-clemente/quic-go" - ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr-net" - "github.com/whyrusleeping/mafmt" -) - -type dialer struct { - transport tpt.Transport -} - -var _ tpt.Dialer = &dialer{} - -func newDialer(transport tpt.Transport) (*dialer, error) { - return &dialer{ - transport: transport, - }, nil -} - -func (d *dialer) Dial(raddr ma.Multiaddr) (tpt.Conn, error) { - // TODO: check that raddr is a QUIC address - _, host, err := manet.DialArgs(raddr) - if err != nil { - return nil, err - } - - qsess, err := quic.DialAddr(host, &tls.Config{InsecureSkipVerify: true}, nil) - if err != nil { - return nil, err - } - - return newQuicConn(qsess, d.transport) -} - -func (d *dialer) DialContext(ctx context.Context, raddr ma.Multiaddr) (tpt.Conn, error) { - // TODO: implement the ctx - return d.Dial(raddr) -} - -func (d *dialer) Matches(a ma.Multiaddr) bool { - return mafmt.QUIC.Matches(a) -} diff --git a/p2p/transport/quic/dialer_test.go b/p2p/transport/quic/dialer_test.go deleted file mode 100644 index 229e19a606..0000000000 --- a/p2p/transport/quic/dialer_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package libp2pquic - -import ( - tpt "github.com/libp2p/go-libp2p-transport" - ma "github.com/multiformats/go-multiaddr" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -var _ = Describe("Listener", func() { - var ( - d *dialer - transport tpt.Transport - ) - - BeforeEach(func() { - var err error - transport = &QuicTransport{} - d, err = newDialer(transport) - Expect(err).ToNot(HaveOccurred()) - }) - - It("dials", func() { - addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/8888") - Expect(err).ToNot(HaveOccurred()) - - // start a listener to connect to - var ln *listener - go func() { - defer GinkgoRecover() - ln, err = newListener(addr, transport) - Expect(err).ToNot(HaveOccurred()) - _, err = ln.Accept() - Expect(err).ToNot(HaveOccurred()) - }() - - Eventually(func() *listener { return ln }).ShouldNot(BeNil()) - conn, err := d.Dial(addr) - Expect(err).ToNot(HaveOccurred()) - Expect(conn.Transport()).To(Equal(d.transport)) - }) - - It("matches", func() { - invalidAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234") - Expect(err).ToNot(HaveOccurred()) - validAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234/quic") - Expect(err).ToNot(HaveOccurred()) - Expect(d.Matches(invalidAddr)).To(BeFalse()) - Expect(d.Matches(validAddr)).To(BeTrue()) - }) -}) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index b0d61c6966..ab044add60 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -1,91 +1,100 @@ package libp2pquic import ( - "crypto/rand" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "encoding/pem" - "math/big" "net" + ic "github.com/libp2p/go-libp2p-crypto" + peer "github.com/libp2p/go-libp2p-peer" tpt "github.com/libp2p/go-libp2p-transport" quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" ) +var quicListenAddr = quic.ListenAddr + +// A listener listens for QUIC connections. type listener struct { - laddr ma.Multiaddr quicListener quic.Listener + transport tpt.Transport + + acceptQueue chan tpt.Conn - transport tpt.Transport + privKey ic.PrivKey + localPeer peer.ID + localMultiaddr ma.Multiaddr } var _ tpt.Listener = &listener{} -func newListener(laddr ma.Multiaddr, t tpt.Transport) (*listener, error) { - _, host, err := manet.DialArgs(laddr) +func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, key ic.PrivKey) (tpt.Listener, error) { + _, host, err := manet.DialArgs(addr) if err != nil { return nil, err } - tlsConf, err := generateTLSConfig() + tlsConf, err := GenerateConfig(key) if err != nil { return nil, err } - qln, err := quic.ListenAddr(host, tlsConf, nil) + ln, err := quicListenAddr(host, tlsConf, &quic.Config{Versions: []quic.VersionNumber{101}}) if err != nil { return nil, err } - addr, err := quicMultiAddress(qln.Addr()) + localMultiaddr, err := quicMultiaddr(ln.Addr()) if err != nil { return nil, err } - return &listener{ - laddr: addr, - quicListener: qln, - transport: t, + quicListener: ln, + transport: transport, + privKey: key, + localPeer: localPeer, + localMultiaddr: localMultiaddr, }, nil } +// Accept accepts new connections. +// TODO(#2): don't accept a connection if the client's peer verification fails func (l *listener) Accept() (tpt.Conn, error) { sess, err := l.quicListener.Accept() if err != nil { return nil, err } - return newQuicConn(sess, l.transport) + remotePubKey, err := getRemotePubKey(sess) + if err != nil { + return nil, err + } + remotePeerID, err := peer.IDFromPublicKey(remotePubKey) + if err != nil { + return nil, err + } + remoteMultiaddr, err := quicMultiaddr(sess.RemoteAddr()) + if err != nil { + return nil, err + } + return &conn{ + sess: sess, + transport: l.transport, + localPeer: l.localPeer, + localMultiaddr: l.localMultiaddr, + privKey: l.privKey, + remoteMultiaddr: remoteMultiaddr, + remotePeerID: remotePeerID, + remotePubKey: remotePubKey, + }, nil } +// Close closes the listener. func (l *listener) Close() error { return l.quicListener.Close() } +// Addr returns the address of this listener. func (l *listener) Addr() net.Addr { return l.quicListener.Addr() } +// Multiaddr returns the multiaddress of this listener. func (l *listener) Multiaddr() ma.Multiaddr { - return l.laddr -} - -// Generate a bare-bones TLS config for the server. -// The client doesn't verify the certificate yet. -func generateTLSConfig() (*tls.Config, error) { - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, err - } - template := x509.Certificate{SerialNumber: big.NewInt(1)} - certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) - if err != nil { - return nil, err - } - keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) - certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) - tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) - if err != nil { - return nil, err - } - return &tls.Config{Certificates: []tls.Certificate{tlsCert}}, nil + return l.localMultiaddr } diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index f4e03d63c8..1069d57e99 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -1,81 +1,67 @@ package libp2pquic import ( - "errors" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "fmt" "net" + ic "github.com/libp2p/go-libp2p-crypto" tpt "github.com/libp2p/go-libp2p-transport" - quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) -type mockQuicListener struct { - connToAccept net.Conn - serveErr error - closed bool - - sessionToAccept quic.Session - acceptErr error -} - -var _ quic.Listener = &mockQuicListener{} - -func (m *mockQuicListener) Close() error { m.closed = true; return nil } -func (m *mockQuicListener) Accept() (quic.Session, error) { return m.sessionToAccept, m.acceptErr } -func (m *mockQuicListener) Addr() net.Addr { panic("not implemented") } - var _ = Describe("Listener", func() { var ( - l *listener - quicListener *mockQuicListener - transport tpt.Transport + t tpt.Transport + localAddr ma.Multiaddr ) BeforeEach(func() { - quicListener = &mockQuicListener{} - transport = &QuicTransport{} - l = &listener{ - quicListener: quicListener, - transport: transport, - } - }) - - It("returns its addr", func() { - laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + rsaKey, err := rsa.GenerateKey(rand.Reader, 1024) + Expect(err).ToNot(HaveOccurred()) + key, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(rsaKey)) + Expect(err).ToNot(HaveOccurred()) + t, err = NewTransport(key) Expect(err).ToNot(HaveOccurred()) - l, err = newListener(laddr, nil) + localAddr, err = ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") Expect(err).ToNot(HaveOccurred()) - as := l.Addr().String() - Expect(as).ToNot(Equal("127.0.0.1:0)")) - Expect(as).To(ContainSubstring("127.0.0.1:")) }) - It("returns its multiaddr", func() { - laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + It("returns the address it is listening on", func() { + ln, err := t.Listen(localAddr) Expect(err).ToNot(HaveOccurred()) - l, err = newListener(laddr, nil) - Expect(err).ToNot(HaveOccurred()) - mas := l.Multiaddr().String() - Expect(mas).ToNot(Equal("/ip4/127.0.0.1/udp/0/quic")) - Expect(mas).To(ContainSubstring("/ip4/127.0.0.1/udp/")) - Expect(mas).To(ContainSubstring("/quic")) + netAddr := ln.Addr() + Expect(netAddr).To(BeAssignableToTypeOf(&net.UDPAddr{})) + port := netAddr.(*net.UDPAddr).Port + Expect(port).ToNot(BeZero()) + Expect(ln.Multiaddr().String()).To(Equal(fmt.Sprintf("/ip4/127.0.0.1/udp/%d/quic", port))) }) - It("closes", func() { - err := l.Close() + It("returns Accept when it is closed", func() { + addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + Expect(err).ToNot(HaveOccurred()) + ln, err := t.Listen(addr) Expect(err).ToNot(HaveOccurred()) - Expect(quicListener.closed).To(BeTrue()) + done := make(chan struct{}) + go func() { + defer GinkgoRecover() + ln.Accept() + close(done) + }() + Consistently(done).ShouldNot(BeClosed()) + Expect(ln.Close()).To(Succeed()) + Eventually(done).Should(BeClosed()) }) - Context("accepting", func() { - It("errors if no connection can be accepted", func() { - testErr := errors.New("test error") - quicListener.acceptErr = testErr - _, err := l.Accept() - Expect(err).To(MatchError(testErr)) - }) + It("doesn't accept Accept calls after it is closed", func() { + ln, err := t.Listen(localAddr) + Expect(err).ToNot(HaveOccurred()) + Expect(ln.Close()).To(Succeed()) + _, err = ln.Accept() + Expect(err).To(HaveOccurred()) }) }) diff --git a/p2p/transport/quic/stream.go b/p2p/transport/quic/stream.go index 5d5ad242cf..3fcf0d7d79 100644 --- a/p2p/transport/quic/stream.go +++ b/p2p/transport/quic/stream.go @@ -2,7 +2,7 @@ package libp2pquic import ( smux "github.com/libp2p/go-stream-muxer" - "github.com/lucas-clemente/quic-go" + quic "github.com/lucas-clemente/quic-go" ) type stream struct { @@ -12,6 +12,8 @@ type stream struct { var _ smux.Stream = &stream{} func (s *stream) Reset() error { - s.Stream.Reset(nil) - return nil + if err := s.Stream.CancelRead(0); err != nil { + return err + } + return s.Stream.CancelWrite(0) } diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 04f6a82751..6e05f63d64 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -1,83 +1,97 @@ package libp2pquic import ( - "fmt" - "sync" + "context" + "errors" + ic "github.com/libp2p/go-libp2p-crypto" + peer "github.com/libp2p/go-libp2p-peer" tpt "github.com/libp2p/go-libp2p-transport" + quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" "github.com/whyrusleeping/mafmt" ) -// QuicTransport implements a QUIC Transport -type QuicTransport struct { - lmutex sync.Mutex - listeners map[string]tpt.Listener +var quicDialAddr = quic.DialAddr - dmutex sync.Mutex - dialers map[string]tpt.Dialer +// The Transport implements the tpt.Transport interface for QUIC connections. +type transport struct { + privKey ic.PrivKey + localPeer peer.ID } -var _ tpt.Transport = &QuicTransport{} +var _ tpt.Transport = &transport{} -// NewQuicTransport creates a new QUIC Transport -// it tracks dialers and listeners created -func NewQuicTransport() *QuicTransport { - // utils.SetLogLevel(utils.LogLevelDebug) - return &QuicTransport{ - listeners: make(map[string]tpt.Listener), - dialers: make(map[string]tpt.Dialer), +// NewTransport creates a new QUIC transport +func NewTransport(key ic.PrivKey) (tpt.Transport, error) { + localPeer, err := peer.IDFromPrivateKey(key) + if err != nil { + return nil, err } + return &transport{ + privKey: key, + localPeer: localPeer, + }, nil } -func (t *QuicTransport) Dialer(laddr ma.Multiaddr, opts ...tpt.DialOpt) (tpt.Dialer, error) { - if !t.Matches(laddr) { - return nil, fmt.Errorf("quic transport cannot dial %q", laddr) - } - - t.dmutex.Lock() - defer t.dmutex.Unlock() - - s := laddr.String() - d, ok := t.dialers[s] - if ok { - return d, nil +// Dial dials a new QUIC connection +func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tpt.Conn, error) { + _, host, err := manet.DialArgs(raddr) + if err != nil { + return nil, err } - - // TODO: read opts - quicd, err := newDialer(t) + tlsConf, err := GenerateConfig(t.privKey) if err != nil { return nil, err } - t.dialers[s] = quicd - return quicd, nil -} - -// Listen starts listening on laddr -func (t *QuicTransport) Listen(laddr ma.Multiaddr) (tpt.Listener, error) { - if !t.Matches(laddr) { - return nil, fmt.Errorf("quic transport cannot listen on %q", laddr) + sess, err := quicDialAddr(host, tlsConf, &quic.Config{Versions: []quic.VersionNumber{101}}) + if err != nil { + return nil, err } - - t.lmutex.Lock() - defer t.lmutex.Unlock() - - l, ok := t.listeners[laddr.String()] - if ok { - return l, nil + remotePubKey, err := getRemotePubKey(sess) + if err != nil { + return nil, err } - - ln, err := newListener(laddr, t) + localMultiaddr, err := quicMultiaddr(sess.LocalAddr()) if err != nil { return nil, err } + if !p.MatchesPublicKey(remotePubKey) { + err := errors.New("peer IDs don't match") + sess.Close(err) + return nil, err + } + return &conn{ + privKey: t.privKey, + localPeer: t.localPeer, + localMultiaddr: localMultiaddr, + remotePubKey: remotePubKey, + remotePeerID: p, + remoteMultiaddr: raddr, + }, nil +} + +// CanDial determines if we can dial to an address +func (t *transport) CanDial(addr ma.Multiaddr) bool { + return mafmt.QUIC.Matches(addr) +} - t.listeners[laddr.String()] = ln - return ln, nil +// Listen listens for new QUIC connections on the passed multiaddr. +func (t *transport) Listen(addr ma.Multiaddr) (tpt.Listener, error) { + return newListener(addr, t, t.localPeer, t.privKey) } -func (t *QuicTransport) Matches(a ma.Multiaddr) bool { - return mafmt.QUIC.Matches(a) +// Proxy returns true if this transport proxies. +func (t *transport) Proxy() bool { + return false } -var _ tpt.Transport = &QuicTransport{} +// Protocols returns the set of protocols handled by this transport. +func (t *transport) Protocols() []int { + return []int{ma.P_QUIC} +} + +func (t *transport) String() string { + return "QUIC" +} diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go index 141e468426..0decd920a7 100644 --- a/p2p/transport/quic/transport_test.go +++ b/p2p/transport/quic/transport_test.go @@ -1,6 +1,7 @@ package libp2pquic import ( + tpt "github.com/libp2p/go-libp2p-transport" ma "github.com/multiformats/go-multiaddr" . "github.com/onsi/ginkgo" @@ -8,75 +9,24 @@ import ( ) var _ = Describe("Transport", func() { - var t *QuicTransport + var t tpt.Transport BeforeEach(func() { - t = NewQuicTransport() + t = &transport{} }) - Context("listening", func() { - It("creates a new listener", func() { - maddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234/quic") - Expect(err).ToNot(HaveOccurred()) - ln, err := t.Listen(maddr) - Expect(err).ToNot(HaveOccurred()) - Expect(ln.Multiaddr()).To(Equal(maddr)) - }) - - It("returns an existing listener", func() { - maddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1235/quic") - Expect(err).ToNot(HaveOccurred()) - ln, err := t.Listen(maddr) - Expect(err).ToNot(HaveOccurred()) - Expect(ln.Multiaddr()).To(Equal(maddr)) - ln2, err := t.Listen(maddr) - Expect(err).ToNot(HaveOccurred()) - Expect(ln2).To(Equal(ln)) - Expect(t.listeners).To(HaveLen(1)) - }) - - It("errors if the address is not a QUIC address", func() { - maddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1235/utp") - Expect(err).ToNot(HaveOccurred()) - _, err = t.Listen(maddr) - Expect(err).To(MatchError("quic transport cannot listen on \"/ip4/127.0.0.1/udp/1235/utp\"")) - }) - }) - - Context("dialing", func() { - It("creates a new dialer", func() { - maddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234/quic") - Expect(err).ToNot(HaveOccurred()) - d, err := t.Dialer(maddr) - Expect(err).ToNot(HaveOccurred()) - Expect(d).ToNot(BeNil()) - }) - - It("returns an existing dialer", func() { - maddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1235/quic") - Expect(err).ToNot(HaveOccurred()) - d, err := t.Dialer(maddr) - Expect(err).ToNot(HaveOccurred()) - d2, err := t.Dialer(maddr) - Expect(err).ToNot(HaveOccurred()) - Expect(d2).To(Equal(d)) - Expect(t.dialers).To(HaveLen(1)) - }) - - It("errors if the address is not a QUIC address", func() { - maddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1235/utp") - Expect(err).ToNot(HaveOccurred()) - _, err = t.Dialer(maddr) - Expect(err).To(MatchError("quic transport cannot dial \"/ip4/127.0.0.1/udp/1235/utp\"")) - }) - }) - - It("matches", func() { + It("says if it can dial an address", func() { invalidAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234") Expect(err).ToNot(HaveOccurred()) validAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234/quic") Expect(err).ToNot(HaveOccurred()) - Expect(t.Matches(invalidAddr)).To(BeFalse()) - Expect(t.Matches(validAddr)).To(BeTrue()) + Expect(t.CanDial(invalidAddr)).To(BeFalse()) + Expect(t.CanDial(validAddr)).To(BeTrue()) + }) + + It("supports the QUIC protocol", func() { + protocols := t.Protocols() + Expect(protocols).To(HaveLen(1)) + Expect(protocols[0]).To(Equal(ma.P_QUIC)) }) }) From 1b0d97811f07a32b01c3edf6d85af81a8cc4ae66 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 2 Feb 2018 11:14:00 +0800 Subject: [PATCH 026/138] verify the server's certificate using tls.Config.VerifyPeerCertificate Fixes #2. --- p2p/transport/quic/conn_test.go | 8 ++++---- p2p/transport/quic/crypto.go | 17 +++++------------ p2p/transport/quic/listener.go | 2 +- p2p/transport/quic/transport.go | 30 +++++++++++++++++++++--------- 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 473a50c6e9..66d9d2f7cb 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -80,14 +80,14 @@ var _ = Describe("Connection", func() { thirdPartyID, err := peer.IDFromPrivateKey(createPeer()) Expect(err).ToNot(HaveOccurred()) - serverAddrChan, _ := runServer() + serverAddrChan, serverConnChan := runServer() clientTransport, err := NewTransport(clientKey) Expect(err).ToNot(HaveOccurred()) serverAddr := <-serverAddrChan // dial, but expect the wrong peer ID _, err = clientTransport.Dial(context.Background(), serverAddr, thirdPartyID) - Expect(err).To(MatchError("peer IDs don't match")) - // TODO(#2): don't accept a connection if the client's peer verification fails - // Consistently(serverConnChan).ShouldNot(Receive()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("TLS handshake error: bad certificate")) + Consistently(serverConnChan).ShouldNot(Receive()) }) }) diff --git a/p2p/transport/quic/crypto.go b/p2p/transport/quic/crypto.go index a6dee953d7..1c6a236000 100644 --- a/p2p/transport/quic/crypto.go +++ b/p2p/transport/quic/crypto.go @@ -10,8 +10,6 @@ import ( "math/big" "time" - "github.com/lucas-clemente/quic-go" - "github.com/gogo/protobuf/proto" ic "github.com/libp2p/go-libp2p-crypto" pb "github.com/libp2p/go-libp2p-crypto/pb" @@ -20,10 +18,6 @@ import ( // mint certificate selection is broken. const hostname = "quic.ipfs" -type connectionStater interface { - ConnectionState() quic.ConnectionState -} - // TODO: make this private func GenerateConfig(privKey ic.PrivKey) (*tls.Config, error) { key, hostCert, err := keyToCertificate(privKey) @@ -63,17 +57,16 @@ func GenerateConfig(privKey ic.PrivKey) (*tls.Config, error) { }, nil } -func getRemotePubKey(conn connectionStater) (ic.PubKey, error) { - certChain := conn.ConnectionState().PeerCertificates - if len(certChain) != 2 { +func getRemotePubKey(chain []*x509.Certificate) (ic.PubKey, error) { + if len(chain) != 2 { return nil, errors.New("expected 2 certificates in the chain") } pool := x509.NewCertPool() - pool.AddCert(certChain[1]) - if _, err := certChain[0].Verify(x509.VerifyOptions{Roots: pool}); err != nil { + pool.AddCert(chain[1]) + if _, err := chain[0].Verify(x509.VerifyOptions{Roots: pool}); err != nil { return nil, err } - remotePubKey, err := x509.MarshalPKIXPublicKey(certChain[1].PublicKey) + remotePubKey, err := x509.MarshalPKIXPublicKey(chain[1].PublicKey) if err != nil { return nil, err } diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index ab044add60..3ff6b8945a 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -60,7 +60,7 @@ func (l *listener) Accept() (tpt.Conn, error) { if err != nil { return nil, err } - remotePubKey, err := getRemotePubKey(sess) + remotePubKey, err := getRemotePubKey(sess.ConnectionState().PeerCertificates) if err != nil { return nil, err } diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 6e05f63d64..c655569d30 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -2,6 +2,7 @@ package libp2pquic import ( "context" + "crypto/x509" "errors" ic "github.com/libp2p/go-libp2p-crypto" @@ -45,11 +46,27 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if err != nil { return nil, err } - sess, err := quicDialAddr(host, tlsConf, &quic.Config{Versions: []quic.VersionNumber{101}}) - if err != nil { - return nil, err + var remotePubKey ic.PubKey + tlsConf.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error { + chain := make([]*x509.Certificate, len(rawCerts)) + for i := 0; i < len(rawCerts); i++ { + cert, err := x509.ParseCertificate(rawCerts[i]) + if err != nil { + return err + } + chain[i] = cert + } + var err error + remotePubKey, err = getRemotePubKey(chain) + if err != nil { + return err + } + if !p.MatchesPublicKey(remotePubKey) { + return errors.New("peer IDs don't match") + } + return nil } - remotePubKey, err := getRemotePubKey(sess) + sess, err := quicDialAddr(host, tlsConf, &quic.Config{Versions: []quic.VersionNumber{101}}) if err != nil { return nil, err } @@ -57,11 +74,6 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if err != nil { return nil, err } - if !p.MatchesPublicKey(remotePubKey) { - err := errors.New("peer IDs don't match") - sess.Close(err) - return nil, err - } return &conn{ privKey: t.privKey, localPeer: t.localPeer, From 55a8a75431a6f10ff318bbc6f229f21de6f19872 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 22 Feb 2018 12:32:38 +0800 Subject: [PATCH 027/138] fix opening and accepting of streams --- p2p/transport/quic/conn_test.go | 21 +++++++++++++++++++++ p2p/transport/quic/transport.go | 1 + 2 files changed, 22 insertions(+) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 66d9d2f7cb..765e711fc8 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" + "io/ioutil" ic "github.com/libp2p/go-libp2p-crypto" peer "github.com/libp2p/go-libp2p-peer" @@ -76,6 +77,26 @@ var _ = Describe("Connection", func() { Expect(serverConn.RemotePublicKey()).To(Equal(clientKey.GetPublic())) }) + It("opens and accepts streams", func() { + serverAddrChan, serverConnChan := runServer() + clientTransport, err := NewTransport(clientKey) + Expect(err).ToNot(HaveOccurred()) + conn, err := clientTransport.Dial(context.Background(), <-serverAddrChan, serverID) + Expect(err).ToNot(HaveOccurred()) + serverConn := <-serverConnChan + + str, err := conn.OpenStream() + Expect(err).ToNot(HaveOccurred()) + _, err = str.Write([]byte("foobar")) + Expect(err).ToNot(HaveOccurred()) + str.Close() + sstr, err := serverConn.AcceptStream() + Expect(err).ToNot(HaveOccurred()) + data, err := ioutil.ReadAll(sstr) + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal([]byte("foobar"))) + }) + It("fails if the peer ID doesn't match", func() { thirdPartyID, err := peer.IDFromPrivateKey(createPeer()) Expect(err).ToNot(HaveOccurred()) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index c655569d30..5339a0ab85 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -75,6 +75,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp return nil, err } return &conn{ + sess: sess, privKey: t.privKey, localPeer: t.localPeer, localMultiaddr: localMultiaddr, From 93810dcfc9bbb7f6dbaf13fbd18bdff509425c5e Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 22 Feb 2018 12:38:57 +0800 Subject: [PATCH 028/138] privatize generating the tls.Config --- p2p/transport/quic/crypto.go | 3 +-- p2p/transport/quic/listener.go | 2 +- p2p/transport/quic/transport.go | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/p2p/transport/quic/crypto.go b/p2p/transport/quic/crypto.go index 1c6a236000..5834a83fcc 100644 --- a/p2p/transport/quic/crypto.go +++ b/p2p/transport/quic/crypto.go @@ -18,8 +18,7 @@ import ( // mint certificate selection is broken. const hostname = "quic.ipfs" -// TODO: make this private -func GenerateConfig(privKey ic.PrivKey) (*tls.Config, error) { +func generateConfig(privKey ic.PrivKey) (*tls.Config, error) { key, hostCert, err := keyToCertificate(privKey) if err != nil { return nil, err diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 3ff6b8945a..b85800c57e 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -32,7 +32,7 @@ func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, if err != nil { return nil, err } - tlsConf, err := GenerateConfig(key) + tlsConf, err := generateConfig(key) if err != nil { return nil, err } diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 5339a0ab85..eae98de6eb 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -42,7 +42,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if err != nil { return nil, err } - tlsConf, err := GenerateConfig(t.privKey) + tlsConf, err := generateConfig(t.privKey) if err != nil { return nil, err } From 11564d0a35a921015355becedd9b5a235bcba915 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 6 Jun 2018 07:25:43 +0200 Subject: [PATCH 029/138] generate the certificate chain on initialisation --- p2p/transport/quic/listener.go | 7 ++----- p2p/transport/quic/transport.go | 14 +++++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index b85800c57e..5e2c7cc0cc 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -1,6 +1,7 @@ package libp2pquic import ( + "crypto/tls" "net" ic "github.com/libp2p/go-libp2p-crypto" @@ -27,15 +28,11 @@ type listener struct { var _ tpt.Listener = &listener{} -func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, key ic.PrivKey) (tpt.Listener, error) { +func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, key ic.PrivKey, tlsConf *tls.Config) (tpt.Listener, error) { _, host, err := manet.DialArgs(addr) if err != nil { return nil, err } - tlsConf, err := generateConfig(key) - if err != nil { - return nil, err - } ln, err := quicListenAddr(host, tlsConf, &quic.Config{Versions: []quic.VersionNumber{101}}) if err != nil { return nil, err diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index eae98de6eb..5458fdb9ba 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -2,6 +2,7 @@ package libp2pquic import ( "context" + "crypto/tls" "crypto/x509" "errors" @@ -20,6 +21,7 @@ var quicDialAddr = quic.DialAddr type transport struct { privKey ic.PrivKey localPeer peer.ID + tlsConf *tls.Config } var _ tpt.Transport = &transport{} @@ -30,9 +32,14 @@ func NewTransport(key ic.PrivKey) (tpt.Transport, error) { if err != nil { return nil, err } + tlsConf, err := generateConfig(key) + if err != nil { + return nil, err + } return &transport{ privKey: key, localPeer: localPeer, + tlsConf: tlsConf, }, nil } @@ -42,11 +49,8 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if err != nil { return nil, err } - tlsConf, err := generateConfig(t.privKey) - if err != nil { - return nil, err - } var remotePubKey ic.PubKey + tlsConf := t.tlsConf.Clone() tlsConf.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error { chain := make([]*x509.Certificate, len(rawCerts)) for i := 0; i < len(rawCerts); i++ { @@ -92,7 +96,7 @@ func (t *transport) CanDial(addr ma.Multiaddr) bool { // Listen listens for new QUIC connections on the passed multiaddr. func (t *transport) Listen(addr ma.Multiaddr) (tpt.Listener, error) { - return newListener(addr, t, t.localPeer, t.privKey) + return newListener(addr, t, t.localPeer, t.privKey, t.tlsConf) } // Proxy returns true if this transport proxies. From 3cb5f3d15137f43df8b01c64eb021d17195a4a23 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 6 Jun 2018 07:27:43 +0200 Subject: [PATCH 030/138] use the same quic.Config values for dialing and listening --- p2p/transport/quic/listener.go | 2 +- p2p/transport/quic/transport.go | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 5e2c7cc0cc..aedd4ece92 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -33,7 +33,7 @@ func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, if err != nil { return nil, err } - ln, err := quicListenAddr(host, tlsConf, &quic.Config{Versions: []quic.VersionNumber{101}}) + ln, err := quicListenAddr(host, tlsConf, quicConfig) if err != nil { return nil, err } diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 5458fdb9ba..cebdc9caff 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -15,6 +15,12 @@ import ( "github.com/whyrusleeping/mafmt" ) +var quicConfig = &quic.Config{ + MaxReceiveStreamFlowControlWindow: 3 * (1 << 20), // 3 MB + MaxReceiveConnectionFlowControlWindow: 4.5 * (1 << 20), // 4.5 MB + Versions: []quic.VersionNumber{101}, +} + var quicDialAddr = quic.DialAddr // The Transport implements the tpt.Transport interface for QUIC connections. @@ -70,7 +76,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp } return nil } - sess, err := quicDialAddr(host, tlsConf, &quic.Config{Versions: []quic.VersionNumber{101}}) + sess, err := quicDialAddr(host, tlsConf, quicConfig) if err != nil { return nil, err } From aeda168d9e692ef2b6c7c7339f684f74e8de18f1 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 11 Jun 2018 07:44:27 +0200 Subject: [PATCH 031/138] remove unused acceptQueue from the listener --- p2p/transport/quic/listener.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index aedd4ece92..12b7f53a9e 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -19,8 +19,6 @@ type listener struct { quicListener quic.Listener transport tpt.Transport - acceptQueue chan tpt.Conn - privKey ic.PrivKey localPeer peer.ID localMultiaddr ma.Multiaddr From e08e8cc0d068283a938fa812d849d094da5523c4 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 11 Jun 2018 14:21:29 +0200 Subject: [PATCH 032/138] close and discard a connection if the client's cert chain is invalid --- p2p/transport/quic/conn_test.go | 82 ++++++++++++++++++++++++++++----- p2p/transport/quic/listener.go | 18 ++++++-- p2p/transport/quic/transport.go | 3 ++ 3 files changed, 88 insertions(+), 15 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 765e711fc8..55e81c37c9 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -4,6 +4,7 @@ import ( "context" "crypto/rand" "crypto/rsa" + "crypto/tls" "crypto/x509" "io/ioutil" @@ -30,23 +31,26 @@ var _ = Describe("Connection", func() { return priv } - runServer := func() (<-chan ma.Multiaddr, <-chan tpt.Conn) { - serverTransport, err := NewTransport(serverKey) - Expect(err).ToNot(HaveOccurred()) + runServer := func(tr tpt.Transport) (ma.Multiaddr, <-chan tpt.Conn) { addrChan := make(chan ma.Multiaddr) connChan := make(chan tpt.Conn) go func() { defer GinkgoRecover() addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") Expect(err).ToNot(HaveOccurred()) - ln, err := serverTransport.Listen(addr) + ln, err := tr.Listen(addr) Expect(err).ToNot(HaveOccurred()) addrChan <- ln.Multiaddr() conn, err := ln.Accept() Expect(err).ToNot(HaveOccurred()) connChan <- conn }() - return addrChan, connChan + return <-addrChan, connChan + } + + // modify the cert chain such that verificiation will fail + invalidateCertChain := func(tlsConf *tls.Config) { + tlsConf.Certificates[0].Certificate = [][]byte{tlsConf.Certificates[0].Certificate[0]} } BeforeEach(func() { @@ -60,10 +64,12 @@ var _ = Describe("Connection", func() { }) It("handshakes", func() { - serverAddrChan, serverConnChan := runServer() + serverTransport, err := NewTransport(serverKey) + Expect(err).ToNot(HaveOccurred()) + serverAddr, serverConnChan := runServer(serverTransport) + clientTransport, err := NewTransport(clientKey) Expect(err).ToNot(HaveOccurred()) - serverAddr := <-serverAddrChan conn, err := clientTransport.Dial(context.Background(), serverAddr, serverID) Expect(err).ToNot(HaveOccurred()) serverConn := <-serverConnChan @@ -78,10 +84,13 @@ var _ = Describe("Connection", func() { }) It("opens and accepts streams", func() { - serverAddrChan, serverConnChan := runServer() + serverTransport, err := NewTransport(serverKey) + Expect(err).ToNot(HaveOccurred()) + serverAddr, serverConnChan := runServer(serverTransport) + clientTransport, err := NewTransport(clientKey) Expect(err).ToNot(HaveOccurred()) - conn, err := clientTransport.Dial(context.Background(), <-serverAddrChan, serverID) + conn, err := clientTransport.Dial(context.Background(), serverAddr, serverID) Expect(err).ToNot(HaveOccurred()) serverConn := <-serverConnChan @@ -101,14 +110,65 @@ var _ = Describe("Connection", func() { thirdPartyID, err := peer.IDFromPrivateKey(createPeer()) Expect(err).ToNot(HaveOccurred()) - serverAddrChan, serverConnChan := runServer() + serverTransport, err := NewTransport(serverKey) + Expect(err).ToNot(HaveOccurred()) + serverAddr, serverConnChan := runServer(serverTransport) + clientTransport, err := NewTransport(clientKey) Expect(err).ToNot(HaveOccurred()) - serverAddr := <-serverAddrChan // dial, but expect the wrong peer ID _, err = clientTransport.Dial(context.Background(), serverAddr, thirdPartyID) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("TLS handshake error: bad certificate")) Consistently(serverConnChan).ShouldNot(Receive()) }) + + It("fails if the client presents an invalid cert chain", func() { + serverTransport, err := NewTransport(serverKey) + Expect(err).ToNot(HaveOccurred()) + serverAddr, serverConnChan := runServer(serverTransport) + + clientTransport, err := NewTransport(clientKey) + invalidateCertChain(clientTransport.(*transport).tlsConf) + Expect(err).ToNot(HaveOccurred()) + conn, err := clientTransport.Dial(context.Background(), serverAddr, serverID) + Expect(err).ToNot(HaveOccurred()) + Eventually(func() bool { return conn.IsClosed() }).Should(BeTrue()) + Consistently(serverConnChan).ShouldNot(Receive()) + }) + + It("fails if the server presents an invalid cert chain", func() { + serverTransport, err := NewTransport(serverKey) + invalidateCertChain(serverTransport.(*transport).tlsConf) + Expect(err).ToNot(HaveOccurred()) + serverAddr, serverConnChan := runServer(serverTransport) + + clientTransport, err := NewTransport(clientKey) + Expect(err).ToNot(HaveOccurred()) + _, err = clientTransport.Dial(context.Background(), serverAddr, serverID) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("TLS handshake error: bad certificate")) + Consistently(serverConnChan).ShouldNot(Receive()) + }) + + It("keeps accepting connections after a failed connection attempt", func() { + serverTransport, err := NewTransport(serverKey) + Expect(err).ToNot(HaveOccurred()) + serverAddr, serverConnChan := runServer(serverTransport) + + // first dial with an invalid cert chain + clientTransport1, err := NewTransport(clientKey) + invalidateCertChain(clientTransport1.(*transport).tlsConf) + Expect(err).ToNot(HaveOccurred()) + _, err = clientTransport1.Dial(context.Background(), serverAddr, serverID) + Expect(err).ToNot(HaveOccurred()) + Consistently(serverConnChan).ShouldNot(Receive()) + + // then dial with a valid client + clientTransport2, err := NewTransport(clientKey) + Expect(err).ToNot(HaveOccurred()) + _, err = clientTransport2.Dial(context.Background(), serverAddr, serverID) + Expect(err).ToNot(HaveOccurred()) + Eventually(serverConnChan).Should(Receive()) + }) }) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 12b7f53a9e..a3638dbe98 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -49,12 +49,22 @@ func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, } // Accept accepts new connections. -// TODO(#2): don't accept a connection if the client's peer verification fails func (l *listener) Accept() (tpt.Conn, error) { - sess, err := l.quicListener.Accept() - if err != nil { - return nil, err + for { + sess, err := l.quicListener.Accept() + if err != nil { + return nil, err + } + conn, err := l.setupConn(sess) + if err != nil { + sess.Close(err) + continue + } + return conn, nil } +} + +func (l *listener) setupConn(sess quic.Session) (tpt.Conn, error) { remotePubKey, err := getRemotePubKey(sess.ConnectionState().PeerCertificates) if err != nil { return nil, err diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index cebdc9caff..98b83eccf1 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -57,6 +57,9 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp } var remotePubKey ic.PubKey tlsConf := t.tlsConf.Clone() + // We need to check the peer ID in the VerifyPeerCertificate callback. + // The tls.Config it is also used for listening, and we might also have concurrent dials. + // Clone it so we can check for the specific peer ID we're dialing here. tlsConf.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error { chain := make([]*x509.Certificate, len(rawCerts)) for i := 0; i < len(rawCerts); i++ { From 964a7f10ea4c60a0783bc831ad049fec0c021e92 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 12 Jun 2018 13:46:34 +0200 Subject: [PATCH 033/138] disable source address validation The certificate chain used for the handshake is relatively small (it fits in 2 packets). This reduces the risk that an attacker will use libp2p nodes for reflection attacks. --- p2p/transport/quic/transport.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 98b83eccf1..c782d2a955 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "crypto/x509" "errors" + "net" ic "github.com/libp2p/go-libp2p-crypto" peer "github.com/libp2p/go-libp2p-peer" @@ -19,6 +20,10 @@ var quicConfig = &quic.Config{ MaxReceiveStreamFlowControlWindow: 3 * (1 << 20), // 3 MB MaxReceiveConnectionFlowControlWindow: 4.5 * (1 << 20), // 4.5 MB Versions: []quic.VersionNumber{101}, + AcceptCookie: func(clientAddr net.Addr, cookie *quic.Cookie) bool { + // TODO(#6): require source address validation when under load + return true + }, } var quicDialAddr = quic.DialAddr From 55f0adc1a33f63d1c68e82ad4c9d05695e750a92 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 15 Jun 2018 06:42:44 +0700 Subject: [PATCH 034/138] use the context for dialing --- p2p/transport/quic/transport.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index c782d2a955..a763c73451 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -26,8 +26,6 @@ var quicConfig = &quic.Config{ }, } -var quicDialAddr = quic.DialAddr - // The Transport implements the tpt.Transport interface for QUIC connections. type transport struct { privKey ic.PrivKey @@ -84,7 +82,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp } return nil } - sess, err := quicDialAddr(host, tlsConf, quicConfig) + sess, err := quic.DialAddrContext(ctx, host, tlsConf, quicConfig) if err != nil { return nil, err } From 3439696ebe1636b00e6eb3a3d9dc014f9160753e Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 15 Jun 2018 10:42:19 +0700 Subject: [PATCH 035/138] fix the name of test ginkgo case --- p2p/transport/quic/libp2pquic_suite_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/quic/libp2pquic_suite_test.go b/p2p/transport/quic/libp2pquic_suite_test.go index d3766da3e0..9675f74995 100644 --- a/p2p/transport/quic/libp2pquic_suite_test.go +++ b/p2p/transport/quic/libp2pquic_suite_test.go @@ -7,7 +7,7 @@ import ( "testing" ) -func TestQuicGo(t *testing.T) { +func TestLibp2pQuicTransport(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "libp2p QUIC Transport Suite") } From 3524ae26b7476f073b12b36f3bb32542076fe16f Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 16 Jun 2018 22:23:12 +0700 Subject: [PATCH 036/138] use multiaddr.Encapsulate to create QUIC multiaddrs --- p2p/transport/quic/conn.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index e5ccdc53c8..312da6d1de 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -82,15 +82,14 @@ func (c *conn) Transport() tpt.Transport { return c.transport } -// TODO: there must be a better way to do this func quicMultiaddr(na net.Addr) (ma.Multiaddr, error) { udpMA, err := manet.FromNetAddr(na) if err != nil { return nil, err } - quicMA, err := ma.NewMultiaddr(udpMA.String() + "/quic") + quicMA, err := ma.NewMultiaddr("/quic") if err != nil { return nil, err } - return quicMA, nil + return udpMA.Encapsulate(quicMA), nil } From c0e1cce41218bc160c2bb6b8910c3ba96d112b7d Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 17 Jun 2018 11:56:33 +0700 Subject: [PATCH 037/138] add a function to decapsulate a quic multiaddr --- p2p/transport/quic/conn.go | 15 ------------ p2p/transport/quic/listener.go | 4 +-- p2p/transport/quic/quic_multiaddr.go | 30 +++++++++++++++++++++++ p2p/transport/quic/quic_multiaddr_test.go | 30 +++++++++++++++++++++++ p2p/transport/quic/transport.go | 2 +- 5 files changed, 63 insertions(+), 18 deletions(-) create mode 100644 p2p/transport/quic/quic_multiaddr.go create mode 100644 p2p/transport/quic/quic_multiaddr_test.go diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index 312da6d1de..fef587395f 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -1,15 +1,12 @@ package libp2pquic import ( - "net" - ic "github.com/libp2p/go-libp2p-crypto" peer "github.com/libp2p/go-libp2p-peer" tpt "github.com/libp2p/go-libp2p-transport" smux "github.com/libp2p/go-stream-muxer" quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr-net" ) type conn struct { @@ -81,15 +78,3 @@ func (c *conn) RemoteMultiaddr() ma.Multiaddr { func (c *conn) Transport() tpt.Transport { return c.transport } - -func quicMultiaddr(na net.Addr) (ma.Multiaddr, error) { - udpMA, err := manet.FromNetAddr(na) - if err != nil { - return nil, err - } - quicMA, err := ma.NewMultiaddr("/quic") - if err != nil { - return nil, err - } - return udpMA.Encapsulate(quicMA), nil -} diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index a3638dbe98..52558872d6 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -35,7 +35,7 @@ func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, if err != nil { return nil, err } - localMultiaddr, err := quicMultiaddr(ln.Addr()) + localMultiaddr, err := toQuicMultiaddr(ln.Addr()) if err != nil { return nil, err } @@ -73,7 +73,7 @@ func (l *listener) setupConn(sess quic.Session) (tpt.Conn, error) { if err != nil { return nil, err } - remoteMultiaddr, err := quicMultiaddr(sess.RemoteAddr()) + remoteMultiaddr, err := toQuicMultiaddr(sess.RemoteAddr()) if err != nil { return nil, err } diff --git a/p2p/transport/quic/quic_multiaddr.go b/p2p/transport/quic/quic_multiaddr.go new file mode 100644 index 0000000000..8b182b76ba --- /dev/null +++ b/p2p/transport/quic/quic_multiaddr.go @@ -0,0 +1,30 @@ +package libp2pquic + +import ( + "net" + + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" +) + +var quicMA ma.Multiaddr + +func init() { + var err error + quicMA, err = ma.NewMultiaddr("/quic") + if err != nil { + panic(err) + } +} + +func toQuicMultiaddr(na net.Addr) (ma.Multiaddr, error) { + udpMA, err := manet.FromNetAddr(na) + if err != nil { + return nil, err + } + return udpMA.Encapsulate(quicMA), nil +} + +func fromQuicMultiaddr(addr ma.Multiaddr) (net.Addr, error) { + return manet.ToNetAddr(addr.Decapsulate(quicMA)) +} diff --git a/p2p/transport/quic/quic_multiaddr_test.go b/p2p/transport/quic/quic_multiaddr_test.go new file mode 100644 index 0000000000..48949d3aa1 --- /dev/null +++ b/p2p/transport/quic/quic_multiaddr_test.go @@ -0,0 +1,30 @@ +package libp2pquic + +import ( + "net" + + ma "github.com/multiformats/go-multiaddr" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("QUIC Multiaddr", func() { + It("converts a net.Addr to a QUIC Multiaddr", func() { + addr := &net.UDPAddr{IP: net.IPv4(192, 168, 0, 42), Port: 1337} + maddr, err := toQuicMultiaddr(addr) + Expect(err).ToNot(HaveOccurred()) + Expect(maddr.String()).To(Equal("/ip4/192.168.0.42/udp/1337/quic")) + }) + + It("converts a QUIC Multiaddr to a net.Addr", func() { + maddr, err := ma.NewMultiaddr("/ip4/192.168.0.42/udp/1337/quic") + Expect(err).ToNot(HaveOccurred()) + addr, err := fromQuicMultiaddr(maddr) + Expect(err).ToNot(HaveOccurred()) + Expect(addr).To(BeAssignableToTypeOf(&net.UDPAddr{})) + udpAddr := addr.(*net.UDPAddr) + Expect(udpAddr.IP).To(Equal(net.IPv4(192, 168, 0, 42))) + Expect(udpAddr.Port).To(Equal(1337)) + }) +}) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index a763c73451..909f850837 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -86,7 +86,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if err != nil { return nil, err } - localMultiaddr, err := quicMultiaddr(sess.LocalAddr()) + localMultiaddr, err := toQuicMultiaddr(sess.LocalAddr()) if err != nil { return nil, err } From 308b97b2ac88dea92e7058ee774501337b5dea8e Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 17 Jun 2018 12:55:35 +0700 Subject: [PATCH 038/138] use a single packet conn for all outgoing connections --- p2p/transport/quic/conn_test.go | 73 ++++++++++++++++++++++++++++----- p2p/transport/quic/transport.go | 19 ++++++++- 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 55e81c37c9..1a1e3f4a67 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -1,12 +1,14 @@ package libp2pquic import ( + "bytes" "context" "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "io/ioutil" + "time" ic "github.com/libp2p/go-libp2p-crypto" peer "github.com/libp2p/go-libp2p-peer" @@ -23,12 +25,14 @@ var _ = Describe("Connection", func() { serverID, clientID peer.ID ) - createPeer := func() ic.PrivKey { + createPeer := func() (peer.ID, ic.PrivKey) { key, err := rsa.GenerateKey(rand.Reader, 1024) Expect(err).ToNot(HaveOccurred()) priv, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(key)) Expect(err).ToNot(HaveOccurred()) - return priv + id, err := peer.IDFromPrivateKey(priv) + Expect(err).ToNot(HaveOccurred()) + return id, priv } runServer := func(tr tpt.Transport) (ma.Multiaddr, <-chan tpt.Conn) { @@ -54,13 +58,8 @@ var _ = Describe("Connection", func() { } BeforeEach(func() { - var err error - serverKey = createPeer() - serverID, err = peer.IDFromPrivateKey(serverKey) - Expect(err).ToNot(HaveOccurred()) - clientKey = createPeer() - clientID, err = peer.IDFromPrivateKey(clientKey) - Expect(err).ToNot(HaveOccurred()) + serverID, serverKey = createPeer() + clientID, clientKey = createPeer() }) It("handshakes", func() { @@ -107,8 +106,7 @@ var _ = Describe("Connection", func() { }) It("fails if the peer ID doesn't match", func() { - thirdPartyID, err := peer.IDFromPrivateKey(createPeer()) - Expect(err).ToNot(HaveOccurred()) + thirdPartyID, _ := createPeer() serverTransport, err := NewTransport(serverKey) Expect(err).ToNot(HaveOccurred()) @@ -171,4 +169,57 @@ var _ = Describe("Connection", func() { Expect(err).ToNot(HaveOccurred()) Eventually(serverConnChan).Should(Receive()) }) + + It("dials to two servers at the same time", func() { + serverID2, serverKey2 := createPeer() + + serverTransport, err := NewTransport(serverKey) + Expect(err).ToNot(HaveOccurred()) + serverAddr, serverConnChan := runServer(serverTransport) + serverTransport2, err := NewTransport(serverKey2) + Expect(err).ToNot(HaveOccurred()) + serverAddr2, serverConnChan2 := runServer(serverTransport2) + + data := bytes.Repeat([]byte{'a'}, 5*1<<20) // 5 MB + // wait for both servers to accept a connection + // then send some data + go func() { + for _, c := range []tpt.Conn{<-serverConnChan, <-serverConnChan2} { + go func(conn tpt.Conn) { + defer GinkgoRecover() + str, err := conn.OpenStream() + Expect(err).ToNot(HaveOccurred()) + defer str.Close() + _, err = str.Write(data) + Expect(err).ToNot(HaveOccurred()) + }(c) + } + }() + + clientTransport, err := NewTransport(clientKey) + Expect(err).ToNot(HaveOccurred()) + c1, err := clientTransport.Dial(context.Background(), serverAddr, serverID) + Expect(err).ToNot(HaveOccurred()) + c2, err := clientTransport.Dial(context.Background(), serverAddr2, serverID2) + Expect(err).ToNot(HaveOccurred()) + + done := make(chan struct{}, 2) + // receive the data on both connections at the same time + for _, c := range []tpt.Conn{c1, c2} { + go func(conn tpt.Conn) { + defer GinkgoRecover() + str, err := conn.AcceptStream() + Expect(err).ToNot(HaveOccurred()) + str.Close() + d, err := ioutil.ReadAll(str) + Expect(err).ToNot(HaveOccurred()) + Expect(d).To(Equal(data)) + conn.Close() + done <- struct{}{} + }(c) + } + + Eventually(done, 5*time.Second).Should(Receive()) + Eventually(done, 5*time.Second).Should(Receive()) + }) }) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 909f850837..814906b4d7 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -31,6 +31,7 @@ type transport struct { privKey ic.PrivKey localPeer peer.ID tlsConf *tls.Config + pconn net.PacketConn } var _ tpt.Transport = &transport{} @@ -45,10 +46,22 @@ func NewTransport(key ic.PrivKey) (tpt.Transport, error) { if err != nil { return nil, err } + + // create a packet conn for outgoing connections + addr, err := net.ResolveUDPAddr("udp", "localhost:0") + if err != nil { + return nil, err + } + conn, err := net.ListenUDP("udp", addr) + if err != nil { + return nil, err + } + return &transport{ privKey: key, localPeer: localPeer, tlsConf: tlsConf, + pconn: conn, }, nil } @@ -58,6 +71,10 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if err != nil { return nil, err } + addr, err := fromQuicMultiaddr(raddr) + if err != nil { + return nil, err + } var remotePubKey ic.PubKey tlsConf := t.tlsConf.Clone() // We need to check the peer ID in the VerifyPeerCertificate callback. @@ -82,7 +99,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp } return nil } - sess, err := quic.DialAddrContext(ctx, host, tlsConf, quicConfig) + sess, err := quic.DialContext(ctx, t.pconn, addr, host, tlsConf, quicConfig) if err != nil { return nil, err } From c75c51aecbfbc1a491a5e39c0f49eac40f566d0f Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 10 Jul 2018 12:25:11 +0200 Subject: [PATCH 039/138] use QUIC Keep Alives --- p2p/transport/quic/transport.go | 1 + 1 file changed, 1 insertion(+) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 814906b4d7..70f2d6d357 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -24,6 +24,7 @@ var quicConfig = &quic.Config{ // TODO(#6): require source address validation when under load return true }, + KeepAlive: true, } // The Transport implements the tpt.Transport interface for QUIC connections. From 9a55577ec532567cf931c191c4d68ed2a9fc1c37 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 14 Jul 2018 11:52:35 +0200 Subject: [PATCH 040/138] obey the multiaddr's IP version when listening --- p2p/transport/quic/listener.go | 12 +++- p2p/transport/quic/listener_test.go | 102 ++++++++++++++++++---------- 2 files changed, 77 insertions(+), 37 deletions(-) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 52558872d6..1cdc79e208 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -27,11 +27,19 @@ type listener struct { var _ tpt.Listener = &listener{} func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, key ic.PrivKey, tlsConf *tls.Config) (tpt.Listener, error) { - _, host, err := manet.DialArgs(addr) + lnet, host, err := manet.DialArgs(addr) if err != nil { return nil, err } - ln, err := quicListenAddr(host, tlsConf, quicConfig) + laddr, err := net.ResolveUDPAddr(lnet, host) + if err != nil { + return nil, err + } + conn, err := net.ListenUDP(lnet, laddr) + if err != nil { + return nil, err + } + ln, err := quic.Listen(conn, tlsConf, quicConfig) if err != nil { return nil, err } diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index 1069d57e99..dd7974cf45 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -15,10 +15,7 @@ import ( ) var _ = Describe("Listener", func() { - var ( - t tpt.Transport - localAddr ma.Multiaddr - ) + var t tpt.Transport BeforeEach(func() { rsaKey, err := rsa.GenerateKey(rand.Reader, 1024) @@ -27,41 +24,76 @@ var _ = Describe("Listener", func() { Expect(err).ToNot(HaveOccurred()) t, err = NewTransport(key) Expect(err).ToNot(HaveOccurred()) - localAddr, err = ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") - Expect(err).ToNot(HaveOccurred()) }) - It("returns the address it is listening on", func() { - ln, err := t.Listen(localAddr) - Expect(err).ToNot(HaveOccurred()) - netAddr := ln.Addr() - Expect(netAddr).To(BeAssignableToTypeOf(&net.UDPAddr{})) - port := netAddr.(*net.UDPAddr).Port - Expect(port).ToNot(BeZero()) - Expect(ln.Multiaddr().String()).To(Equal(fmt.Sprintf("/ip4/127.0.0.1/udp/%d/quic", port))) - }) + Context("listening on the right address", func() { + It("returns the address it is listening on", func() { + localAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + ln, err := t.Listen(localAddr) + Expect(err).ToNot(HaveOccurred()) + netAddr := ln.Addr() + Expect(netAddr).To(BeAssignableToTypeOf(&net.UDPAddr{})) + port := netAddr.(*net.UDPAddr).Port + Expect(port).ToNot(BeZero()) + Expect(ln.Multiaddr().String()).To(Equal(fmt.Sprintf("/ip4/127.0.0.1/udp/%d/quic", port))) + }) - It("returns Accept when it is closed", func() { - addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") - Expect(err).ToNot(HaveOccurred()) - ln, err := t.Listen(addr) - Expect(err).ToNot(HaveOccurred()) - done := make(chan struct{}) - go func() { - defer GinkgoRecover() - ln.Accept() - close(done) - }() - Consistently(done).ShouldNot(BeClosed()) - Expect(ln.Close()).To(Succeed()) - Eventually(done).Should(BeClosed()) + It("returns the address it is listening on, for listening on IPv4", func() { + localAddr, err := ma.NewMultiaddr("/ip4/0.0.0.0/udp/0/quic") + Expect(err).ToNot(HaveOccurred()) + ln, err := t.Listen(localAddr) + Expect(err).ToNot(HaveOccurred()) + netAddr := ln.Addr() + Expect(netAddr).To(BeAssignableToTypeOf(&net.UDPAddr{})) + port := netAddr.(*net.UDPAddr).Port + Expect(port).ToNot(BeZero()) + Expect(ln.Multiaddr().String()).To(Equal(fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic", port))) + }) + + It("returns the address it is listening on, for listening on IPv6", func() { + localAddr, err := ma.NewMultiaddr("/ip6/::/udp/0/quic") + Expect(err).ToNot(HaveOccurred()) + ln, err := t.Listen(localAddr) + Expect(err).ToNot(HaveOccurred()) + netAddr := ln.Addr() + Expect(netAddr).To(BeAssignableToTypeOf(&net.UDPAddr{})) + port := netAddr.(*net.UDPAddr).Port + Expect(port).ToNot(BeZero()) + Expect(ln.Multiaddr().String()).To(Equal(fmt.Sprintf("/ip6/::/udp/%d/quic", port))) + }) }) - It("doesn't accept Accept calls after it is closed", func() { - ln, err := t.Listen(localAddr) - Expect(err).ToNot(HaveOccurred()) - Expect(ln.Close()).To(Succeed()) - _, err = ln.Accept() - Expect(err).To(HaveOccurred()) + Context("accepting connections", func() { + var localAddr ma.Multiaddr + + BeforeEach(func() { + var err error + localAddr, err = ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + Expect(err).ToNot(HaveOccurred()) + }) + + It("returns Accept when it is closed", func() { + addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + Expect(err).ToNot(HaveOccurred()) + ln, err := t.Listen(addr) + Expect(err).ToNot(HaveOccurred()) + done := make(chan struct{}) + go func() { + defer GinkgoRecover() + ln.Accept() + close(done) + }() + Consistently(done).ShouldNot(BeClosed()) + Expect(ln.Close()).To(Succeed()) + Eventually(done).Should(BeClosed()) + }) + + It("doesn't accept Accept calls after it is closed", func() { + ln, err := t.Listen(localAddr) + Expect(err).ToNot(HaveOccurred()) + Expect(ln.Close()).To(Succeed()) + _, err = ln.Accept() + Expect(err).To(HaveOccurred()) + }) }) }) From ffe5aa8693a07c80f23d624d857243f7f9a20f1a Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 14 Jul 2018 13:54:36 +0200 Subject: [PATCH 041/138] obey the multiaddr's IP version when dialing --- p2p/transport/quic/conn_test.go | 42 ++++++++++++++----- p2p/transport/quic/transport.go | 71 +++++++++++++++++++++++---------- 2 files changed, 82 insertions(+), 31 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 1a1e3f4a67..d4744882a0 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -35,12 +35,12 @@ var _ = Describe("Connection", func() { return id, priv } - runServer := func(tr tpt.Transport) (ma.Multiaddr, <-chan tpt.Conn) { + runServer := func(tr tpt.Transport, multiaddr string) (ma.Multiaddr, <-chan tpt.Conn) { addrChan := make(chan ma.Multiaddr) connChan := make(chan tpt.Conn) go func() { defer GinkgoRecover() - addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + addr, err := ma.NewMultiaddr(multiaddr) Expect(err).ToNot(HaveOccurred()) ln, err := tr.Listen(addr) Expect(err).ToNot(HaveOccurred()) @@ -62,10 +62,30 @@ var _ = Describe("Connection", func() { clientID, clientKey = createPeer() }) - It("handshakes", func() { + It("handshakes on IPv4", func() { serverTransport, err := NewTransport(serverKey) Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport) + serverAddr, serverConnChan := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") + + clientTransport, err := NewTransport(clientKey) + Expect(err).ToNot(HaveOccurred()) + conn, err := clientTransport.Dial(context.Background(), serverAddr, serverID) + Expect(err).ToNot(HaveOccurred()) + serverConn := <-serverConnChan + Expect(conn.LocalPeer()).To(Equal(clientID)) + Expect(conn.LocalPrivateKey()).To(Equal(clientKey)) + Expect(conn.RemotePeer()).To(Equal(serverID)) + Expect(conn.RemotePublicKey()).To(Equal(serverKey.GetPublic())) + Expect(serverConn.LocalPeer()).To(Equal(serverID)) + Expect(serverConn.LocalPrivateKey()).To(Equal(serverKey)) + Expect(serverConn.RemotePeer()).To(Equal(clientID)) + Expect(serverConn.RemotePublicKey()).To(Equal(clientKey.GetPublic())) + }) + + It("handshakes on IPv6", func() { + serverTransport, err := NewTransport(serverKey) + Expect(err).ToNot(HaveOccurred()) + serverAddr, serverConnChan := runServer(serverTransport, "/ip6/::1/udp/0/quic") clientTransport, err := NewTransport(clientKey) Expect(err).ToNot(HaveOccurred()) @@ -85,7 +105,7 @@ var _ = Describe("Connection", func() { It("opens and accepts streams", func() { serverTransport, err := NewTransport(serverKey) Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport) + serverAddr, serverConnChan := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") clientTransport, err := NewTransport(clientKey) Expect(err).ToNot(HaveOccurred()) @@ -110,7 +130,7 @@ var _ = Describe("Connection", func() { serverTransport, err := NewTransport(serverKey) Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport) + serverAddr, serverConnChan := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") clientTransport, err := NewTransport(clientKey) Expect(err).ToNot(HaveOccurred()) @@ -124,7 +144,7 @@ var _ = Describe("Connection", func() { It("fails if the client presents an invalid cert chain", func() { serverTransport, err := NewTransport(serverKey) Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport) + serverAddr, serverConnChan := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") clientTransport, err := NewTransport(clientKey) invalidateCertChain(clientTransport.(*transport).tlsConf) @@ -139,7 +159,7 @@ var _ = Describe("Connection", func() { serverTransport, err := NewTransport(serverKey) invalidateCertChain(serverTransport.(*transport).tlsConf) Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport) + serverAddr, serverConnChan := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") clientTransport, err := NewTransport(clientKey) Expect(err).ToNot(HaveOccurred()) @@ -152,7 +172,7 @@ var _ = Describe("Connection", func() { It("keeps accepting connections after a failed connection attempt", func() { serverTransport, err := NewTransport(serverKey) Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport) + serverAddr, serverConnChan := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") // first dial with an invalid cert chain clientTransport1, err := NewTransport(clientKey) @@ -175,10 +195,10 @@ var _ = Describe("Connection", func() { serverTransport, err := NewTransport(serverKey) Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport) + serverAddr, serverConnChan := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") serverTransport2, err := NewTransport(serverKey2) Expect(err).ToNot(HaveOccurred()) - serverAddr2, serverConnChan2 := runServer(serverTransport2) + serverAddr2, serverConnChan2 := runServer(serverTransport2, "/ip4/127.0.0.1/udp/0/quic") data := bytes.Repeat([]byte{'a'}, 5*1<<20) // 5 MB // wait for both servers to accept a connection diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 70f2d6d357..2fb16bec11 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -5,7 +5,9 @@ import ( "crypto/tls" "crypto/x509" "errors" + "fmt" "net" + "sync" ic "github.com/libp2p/go-libp2p-crypto" peer "github.com/libp2p/go-libp2p-peer" @@ -27,12 +29,47 @@ var quicConfig = &quic.Config{ KeepAlive: true, } +type connManager struct { + connIPv4Once sync.Once + connIPv4 net.PacketConn + + connIPv6Once sync.Once + connIPv6 net.PacketConn +} + +func (c *connManager) GetConnForAddr(network string) (net.PacketConn, error) { + switch network { + case "udp4": + var err error + c.connIPv4Once.Do(func() { + c.connIPv4, err = c.createConn(network, "0.0.0.0:0") + }) + return c.connIPv4, err + case "udp6": + var err error + c.connIPv6Once.Do(func() { + c.connIPv6, err = c.createConn(network, ":0") + }) + return c.connIPv6, err + default: + return nil, fmt.Errorf("unsupported network: %s", network) + } +} + +func (c *connManager) createConn(network, host string) (net.PacketConn, error) { + addr, err := net.ResolveUDPAddr(network, host) + if err != nil { + return nil, err + } + return net.ListenUDP(network, addr) +} + // The Transport implements the tpt.Transport interface for QUIC connections. type transport struct { - privKey ic.PrivKey - localPeer peer.ID - tlsConf *tls.Config - pconn net.PacketConn + privKey ic.PrivKey + localPeer peer.ID + tlsConf *tls.Config + connManager *connManager } var _ tpt.Transport = &transport{} @@ -48,27 +85,21 @@ func NewTransport(key ic.PrivKey) (tpt.Transport, error) { return nil, err } - // create a packet conn for outgoing connections - addr, err := net.ResolveUDPAddr("udp", "localhost:0") - if err != nil { - return nil, err - } - conn, err := net.ListenUDP("udp", addr) - if err != nil { - return nil, err - } - return &transport{ - privKey: key, - localPeer: localPeer, - tlsConf: tlsConf, - pconn: conn, + privKey: key, + localPeer: localPeer, + tlsConf: tlsConf, + connManager: &connManager{}, }, nil } // Dial dials a new QUIC connection func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tpt.Conn, error) { - _, host, err := manet.DialArgs(raddr) + network, host, err := manet.DialArgs(raddr) + if err != nil { + return nil, err + } + pconn, err := t.connManager.GetConnForAddr(network) if err != nil { return nil, err } @@ -100,7 +131,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp } return nil } - sess, err := quic.DialContext(ctx, t.pconn, addr, host, tlsConf, quicConfig) + sess, err := quic.DialContext(ctx, pconn, addr, host, tlsConf, quicConfig) if err != nil { return nil, err } From 8dcf45bc813c0441f74fbddef838020e47d63638 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 5 Aug 2018 07:40:08 +0700 Subject: [PATCH 042/138] generate certificates that are valid for 180 days --- p2p/transport/quic/crypto.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/p2p/transport/quic/crypto.go b/p2p/transport/quic/crypto.go index 5834a83fcc..a9aabeefd2 100644 --- a/p2p/transport/quic/crypto.go +++ b/p2p/transport/quic/crypto.go @@ -18,6 +18,8 @@ import ( // mint certificate selection is broken. const hostname = "quic.ipfs" +const certValidityPeriod = 180 * 24 * time.Hour + func generateConfig(privKey ic.PrivKey) (*tls.Config, error) { key, hostCert, err := keyToCertificate(privKey) if err != nil { @@ -35,7 +37,7 @@ func generateConfig(privKey ic.PrivKey) (*tls.Config, error) { DNSNames: []string{hostname}, SerialNumber: big.NewInt(1), NotBefore: time.Now().Add(-24 * time.Hour), - NotAfter: time.Now().Add(30 * 24 * time.Hour), + NotAfter: time.Now().Add(certValidityPeriod), } certDER, err := x509.CreateCertificate(rand.Reader, certTemplate, hostCert, ephemeralKey.Public(), key) if err != nil { @@ -80,7 +82,7 @@ func keyToCertificate(sk ic.PrivKey) (interface{}, *x509.Certificate, error) { tmpl := &x509.Certificate{ SerialNumber: sn, NotBefore: time.Now().Add(-24 * time.Hour), - NotAfter: time.Now().Add(30 * 24 * time.Hour), + NotAfter: time.Now().Add(certValidityPeriod), IsCA: true, BasicConstraintsValid: true, } From c3a5e869081a1852593e7745f82595f4c2b1e21a Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 11 Aug 2018 11:31:00 +0700 Subject: [PATCH 043/138] allow 1000 bidirectional streams, disable unidirectional streams --- p2p/transport/quic/transport.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 2fb16bec11..d13300fa71 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -19,6 +19,8 @@ import ( ) var quicConfig = &quic.Config{ + MaxIncomingStreams: 1000, + MaxIncomingUniStreams: -1, // disable unidirectional streams MaxReceiveStreamFlowControlWindow: 3 * (1 << 20), // 3 MB MaxReceiveConnectionFlowControlWindow: 4.5 * (1 << 20), // 4.5 MB Versions: []quic.VersionNumber{101}, From 005db422e6ef5b57ed60b0ca884c0e82fe106453 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 16 Aug 2018 11:29:43 +0700 Subject: [PATCH 044/138] update quic-go to v0.9.0, use the quic-go milestone version --- p2p/transport/quic/conn.go | 2 +- p2p/transport/quic/listener.go | 2 +- p2p/transport/quic/transport.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index fef587395f..ed837835cb 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -25,7 +25,7 @@ type conn struct { var _ tpt.Conn = &conn{} func (c *conn) Close() error { - return c.sess.Close(nil) + return c.sess.Close() } // IsClosed returns whether a connection is fully closed. diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 1cdc79e208..ef8b019048 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -65,7 +65,7 @@ func (l *listener) Accept() (tpt.Conn, error) { } conn, err := l.setupConn(sess) if err != nil { - sess.Close(err) + sess.CloseWithError(0, err) continue } return conn, nil diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index d13300fa71..d8c984af27 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -19,11 +19,11 @@ import ( ) var quicConfig = &quic.Config{ + Versions: []quic.VersionNumber{quic.VersionMilestone0_9_0}, MaxIncomingStreams: 1000, MaxIncomingUniStreams: -1, // disable unidirectional streams MaxReceiveStreamFlowControlWindow: 3 * (1 << 20), // 3 MB MaxReceiveConnectionFlowControlWindow: 4.5 * (1 << 20), // 4.5 MB - Versions: []quic.VersionNumber{101}, AcceptCookie: func(clientAddr net.Addr, cookie *quic.Cookie) bool { // TODO(#6): require source address validation when under load return true From 88f8b4c38af708e5096c238f0370cfc53cd5aea9 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 28 Aug 2018 12:17:51 +0700 Subject: [PATCH 045/138] update quic-go to 0.10.0 --- p2p/transport/quic/transport.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index d8c984af27..c58b7b0a4e 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -19,7 +19,7 @@ import ( ) var quicConfig = &quic.Config{ - Versions: []quic.VersionNumber{quic.VersionMilestone0_9_0}, + Versions: []quic.VersionNumber{quic.VersionMilestone0_10_0}, MaxIncomingStreams: 1000, MaxIncomingUniStreams: -1, // disable unidirectional streams MaxReceiveStreamFlowControlWindow: 3 * (1 << 20), // 3 MB From 9692260f180abb455362f6a2c7102aad507ba7fe Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 31 Aug 2018 13:35:18 +0700 Subject: [PATCH 046/138] run Go 1.11 gofmt, use Go 1.10.x and 1.11.x on Travis --- p2p/transport/quic/crypto.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/p2p/transport/quic/crypto.go b/p2p/transport/quic/crypto.go index a9aabeefd2..1ce18bcb9d 100644 --- a/p2p/transport/quic/crypto.go +++ b/p2p/transport/quic/crypto.go @@ -80,10 +80,10 @@ func keyToCertificate(sk ic.PrivKey) (interface{}, *x509.Certificate, error) { return nil, nil, err } tmpl := &x509.Certificate{ - SerialNumber: sn, - NotBefore: time.Now().Add(-24 * time.Hour), - NotAfter: time.Now().Add(certValidityPeriod), - IsCA: true, + SerialNumber: sn, + NotBefore: time.Now().Add(-24 * time.Hour), + NotAfter: time.Now().Add(certValidityPeriod), + IsCA: true, BasicConstraintsValid: true, } From b3274ae95015be38b771a7e7f3d2cb098ea7cc9d Mon Sep 17 00:00:00 2001 From: can Date: Tue, 20 Nov 2018 18:00:12 +0800 Subject: [PATCH 047/138] Fix missing transport in dialed connection --- p2p/transport/quic/transport.go | 1 + 1 file changed, 1 insertion(+) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index c58b7b0a4e..212ddf4e46 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -143,6 +143,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp } return &conn{ sess: sess, + transport: t, privKey: t.privKey, localPeer: t.localPeer, localMultiaddr: localMultiaddr, From 925970409796e5dc468dd20f9dfd55015d1f7d93 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 4 Apr 2019 22:13:44 +0900 Subject: [PATCH 048/138] update quic-go to v0.11.0 --- p2p/transport/quic/conn_test.go | 4 ++-- p2p/transport/quic/stream.go | 7 +++---- p2p/transport/quic/transport.go | 1 - 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index d4744882a0..1e0863e77c 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -137,7 +137,7 @@ var _ = Describe("Connection", func() { // dial, but expect the wrong peer ID _, err = clientTransport.Dial(context.Background(), serverAddr, thirdPartyID) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("TLS handshake error: bad certificate")) + Expect(err.Error()).To(ContainSubstring("CRYPTO_ERROR")) Consistently(serverConnChan).ShouldNot(Receive()) }) @@ -165,7 +165,7 @@ var _ = Describe("Connection", func() { Expect(err).ToNot(HaveOccurred()) _, err = clientTransport.Dial(context.Background(), serverAddr, serverID) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("TLS handshake error: bad certificate")) + Expect(err.Error()).To(ContainSubstring("CRYPTO_ERROR")) Consistently(serverConnChan).ShouldNot(Receive()) }) diff --git a/p2p/transport/quic/stream.go b/p2p/transport/quic/stream.go index 3fcf0d7d79..108536560e 100644 --- a/p2p/transport/quic/stream.go +++ b/p2p/transport/quic/stream.go @@ -12,8 +12,7 @@ type stream struct { var _ smux.Stream = &stream{} func (s *stream) Reset() error { - if err := s.Stream.CancelRead(0); err != nil { - return err - } - return s.Stream.CancelWrite(0) + s.Stream.CancelRead(0) + s.Stream.CancelWrite(0) + return nil } diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 212ddf4e46..2351959c00 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -19,7 +19,6 @@ import ( ) var quicConfig = &quic.Config{ - Versions: []quic.VersionNumber{quic.VersionMilestone0_10_0}, MaxIncomingStreams: 1000, MaxIncomingUniStreams: -1, // disable unidirectional streams MaxReceiveStreamFlowControlWindow: 3 * (1 << 20), // 3 MB From 2af204e40d1fa10bef6bf37552bad88906a2bc1c Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 20 Apr 2019 12:06:17 +0900 Subject: [PATCH 049/138] when ListenUDP fails once, try again next time --- p2p/transport/quic/transport.go | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 2351959c00..d9f2c8f09c 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -31,26 +31,30 @@ var quicConfig = &quic.Config{ } type connManager struct { - connIPv4Once sync.Once - connIPv4 net.PacketConn + mutex sync.Mutex - connIPv6Once sync.Once - connIPv6 net.PacketConn + connIPv4 net.PacketConn + connIPv6 net.PacketConn } func (c *connManager) GetConnForAddr(network string) (net.PacketConn, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + switch network { case "udp4": + if c.connIPv4 != nil { + return c.connIPv4, nil + } var err error - c.connIPv4Once.Do(func() { - c.connIPv4, err = c.createConn(network, "0.0.0.0:0") - }) + c.connIPv4, err = c.createConn(network, "0.0.0.0:0") return c.connIPv4, err case "udp6": + if c.connIPv6 != nil { + return c.connIPv6, nil + } var err error - c.connIPv6Once.Do(func() { - c.connIPv6, err = c.createConn(network, ":0") - }) + c.connIPv6, err = c.createConn(network, ":0") return c.connIPv6, err default: return nil, fmt.Errorf("unsupported network: %s", network) From 5a33a88caf0ba70df2866c484c9bc1b6d6c0a5b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Sun, 26 May 2019 17:00:40 +0100 Subject: [PATCH 050/138] migrate to consolidated types. (#62) --- p2p/transport/quic/conn.go | 15 ++++++++------- p2p/transport/quic/conn_test.go | 18 +++++++++--------- p2p/transport/quic/crypto.go | 4 ++-- p2p/transport/quic/listener.go | 11 ++++++----- p2p/transport/quic/listener_test.go | 5 +++-- p2p/transport/quic/stream.go | 5 +++-- p2p/transport/quic/transport.go | 9 +++++---- p2p/transport/quic/transport_test.go | 2 +- 8 files changed, 37 insertions(+), 32 deletions(-) diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index ed837835cb..bf173458a4 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -1,10 +1,11 @@ package libp2pquic import ( - ic "github.com/libp2p/go-libp2p-crypto" - peer "github.com/libp2p/go-libp2p-peer" - tpt "github.com/libp2p/go-libp2p-transport" - smux "github.com/libp2p/go-stream-muxer" + ic "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/mux" + "github.com/libp2p/go-libp2p-core/peer" + tpt "github.com/libp2p/go-libp2p-core/transport" + quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" ) @@ -22,7 +23,7 @@ type conn struct { remoteMultiaddr ma.Multiaddr } -var _ tpt.Conn = &conn{} +var _ tpt.CapableConn = &conn{} func (c *conn) Close() error { return c.sess.Close() @@ -34,13 +35,13 @@ func (c *conn) IsClosed() bool { } // OpenStream creates a new stream. -func (c *conn) OpenStream() (smux.Stream, error) { +func (c *conn) OpenStream() (mux.MuxedStream, error) { qstr, err := c.sess.OpenStreamSync() return &stream{Stream: qstr}, err } // AcceptStream accepts a stream opened by the other side. -func (c *conn) AcceptStream() (smux.Stream, error) { +func (c *conn) AcceptStream() (mux.MuxedStream, error) { qstr, err := c.sess.AcceptStream() return &stream{Stream: qstr}, err } diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 1e0863e77c..8b86af2073 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -10,9 +10,9 @@ import ( "io/ioutil" "time" - ic "github.com/libp2p/go-libp2p-crypto" - peer "github.com/libp2p/go-libp2p-peer" - tpt "github.com/libp2p/go-libp2p-transport" + ic "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + tpt "github.com/libp2p/go-libp2p-core/transport" ma "github.com/multiformats/go-multiaddr" . "github.com/onsi/ginkgo" @@ -35,9 +35,9 @@ var _ = Describe("Connection", func() { return id, priv } - runServer := func(tr tpt.Transport, multiaddr string) (ma.Multiaddr, <-chan tpt.Conn) { + runServer := func(tr tpt.Transport, multiaddr string) (ma.Multiaddr, <-chan tpt.CapableConn) { addrChan := make(chan ma.Multiaddr) - connChan := make(chan tpt.Conn) + connChan := make(chan tpt.CapableConn) go func() { defer GinkgoRecover() addr, err := ma.NewMultiaddr(multiaddr) @@ -204,8 +204,8 @@ var _ = Describe("Connection", func() { // wait for both servers to accept a connection // then send some data go func() { - for _, c := range []tpt.Conn{<-serverConnChan, <-serverConnChan2} { - go func(conn tpt.Conn) { + for _, c := range []tpt.CapableConn{<-serverConnChan, <-serverConnChan2} { + go func(conn tpt.CapableConn) { defer GinkgoRecover() str, err := conn.OpenStream() Expect(err).ToNot(HaveOccurred()) @@ -225,8 +225,8 @@ var _ = Describe("Connection", func() { done := make(chan struct{}, 2) // receive the data on both connections at the same time - for _, c := range []tpt.Conn{c1, c2} { - go func(conn tpt.Conn) { + for _, c := range []tpt.CapableConn{c1, c2} { + go func(conn tpt.CapableConn) { defer GinkgoRecover() str, err := conn.AcceptStream() Expect(err).ToNot(HaveOccurred()) diff --git a/p2p/transport/quic/crypto.go b/p2p/transport/quic/crypto.go index 1ce18bcb9d..68a30a629e 100644 --- a/p2p/transport/quic/crypto.go +++ b/p2p/transport/quic/crypto.go @@ -11,8 +11,8 @@ import ( "time" "github.com/gogo/protobuf/proto" - ic "github.com/libp2p/go-libp2p-crypto" - pb "github.com/libp2p/go-libp2p-crypto/pb" + ic "github.com/libp2p/go-libp2p-core/crypto" + pb "github.com/libp2p/go-libp2p-core/crypto/pb" ) // mint certificate selection is broken. diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index ef8b019048..327af2f4c1 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -4,9 +4,10 @@ import ( "crypto/tls" "net" - ic "github.com/libp2p/go-libp2p-crypto" - peer "github.com/libp2p/go-libp2p-peer" - tpt "github.com/libp2p/go-libp2p-transport" + ic "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + tpt "github.com/libp2p/go-libp2p-core/transport" + quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" @@ -57,7 +58,7 @@ func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, } // Accept accepts new connections. -func (l *listener) Accept() (tpt.Conn, error) { +func (l *listener) Accept() (tpt.CapableConn, error) { for { sess, err := l.quicListener.Accept() if err != nil { @@ -72,7 +73,7 @@ func (l *listener) Accept() (tpt.Conn, error) { } } -func (l *listener) setupConn(sess quic.Session) (tpt.Conn, error) { +func (l *listener) setupConn(sess quic.Session) (tpt.CapableConn, error) { remotePubKey, err := getRemotePubKey(sess.ConnectionState().PeerCertificates) if err != nil { return nil, err diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index dd7974cf45..bf0435f1ac 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -7,8 +7,9 @@ import ( "fmt" "net" - ic "github.com/libp2p/go-libp2p-crypto" - tpt "github.com/libp2p/go-libp2p-transport" + ic "github.com/libp2p/go-libp2p-core/crypto" + tpt "github.com/libp2p/go-libp2p-core/transport" + ma "github.com/multiformats/go-multiaddr" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" diff --git a/p2p/transport/quic/stream.go b/p2p/transport/quic/stream.go index 108536560e..e757de20dd 100644 --- a/p2p/transport/quic/stream.go +++ b/p2p/transport/quic/stream.go @@ -1,7 +1,8 @@ package libp2pquic import ( - smux "github.com/libp2p/go-stream-muxer" + "github.com/libp2p/go-libp2p-core/mux" + quic "github.com/lucas-clemente/quic-go" ) @@ -9,7 +10,7 @@ type stream struct { quic.Stream } -var _ smux.Stream = &stream{} +var _ mux.MuxedStream = &stream{} func (s *stream) Reset() error { s.Stream.CancelRead(0) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index d9f2c8f09c..c33b241766 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -9,9 +9,10 @@ import ( "net" "sync" - ic "github.com/libp2p/go-libp2p-crypto" - peer "github.com/libp2p/go-libp2p-peer" - tpt "github.com/libp2p/go-libp2p-transport" + ic "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + tpt "github.com/libp2p/go-libp2p-core/transport" + quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" @@ -99,7 +100,7 @@ func NewTransport(key ic.PrivKey) (tpt.Transport, error) { } // Dial dials a new QUIC connection -func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tpt.Conn, error) { +func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tpt.CapableConn, error) { network, host, err := manet.DialArgs(raddr) if err != nil { return nil, err diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go index 0decd920a7..8dfece43ba 100644 --- a/p2p/transport/quic/transport_test.go +++ b/p2p/transport/quic/transport_test.go @@ -1,7 +1,7 @@ package libp2pquic import ( - tpt "github.com/libp2p/go-libp2p-transport" + tpt "github.com/libp2p/go-libp2p-core/transport" ma "github.com/multiformats/go-multiaddr" . "github.com/onsi/ginkgo" From 744a6addae2717776e785d45adcab97fdafad9f0 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 5 Aug 2019 08:12:37 +0700 Subject: [PATCH 051/138] update quic-go to v0.12.0 (supporting QUIC draft-22) --- p2p/transport/quic/conn.go | 6 ++++-- p2p/transport/quic/crypto.go | 3 +++ p2p/transport/quic/listener.go | 5 +++-- p2p/transport/quic/transport.go | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index bf173458a4..75e2193692 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -1,6 +1,8 @@ package libp2pquic import ( + "context" + ic "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/mux" "github.com/libp2p/go-libp2p-core/peer" @@ -36,13 +38,13 @@ func (c *conn) IsClosed() bool { // OpenStream creates a new stream. func (c *conn) OpenStream() (mux.MuxedStream, error) { - qstr, err := c.sess.OpenStreamSync() + qstr, err := c.sess.OpenStreamSync(context.Background()) return &stream{Stream: qstr}, err } // AcceptStream accepts a stream opened by the other side. func (c *conn) AcceptStream() (mux.MuxedStream, error) { - qstr, err := c.sess.AcceptStream() + qstr, err := c.sess.AcceptStream(context.Background()) return &stream{Stream: qstr}, err } diff --git a/p2p/transport/quic/crypto.go b/p2p/transport/quic/crypto.go index 68a30a629e..2ce698680f 100644 --- a/p2p/transport/quic/crypto.go +++ b/p2p/transport/quic/crypto.go @@ -18,6 +18,8 @@ import ( // mint certificate selection is broken. const hostname = "quic.ipfs" +const alpn string = "libp2p" + const certValidityPeriod = 180 * 24 * time.Hour func generateConfig(privKey ic.PrivKey) (*tls.Config, error) { @@ -55,6 +57,7 @@ func generateConfig(privKey ic.PrivKey) (*tls.Config, error) { Certificate: [][]byte{cert.Raw, hostCert.Raw}, PrivateKey: ephemeralKey, }}, + NextProtos: []string{alpn}, }, nil } diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 327af2f4c1..2e39854dd2 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -1,6 +1,7 @@ package libp2pquic import ( + "context" "crypto/tls" "net" @@ -60,13 +61,13 @@ func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, // Accept accepts new connections. func (l *listener) Accept() (tpt.CapableConn, error) { for { - sess, err := l.quicListener.Accept() + sess, err := l.quicListener.Accept(context.Background()) if err != nil { return nil, err } conn, err := l.setupConn(sess) if err != nil { - sess.CloseWithError(0, err) + sess.CloseWithError(0, err.Error()) continue } return conn, nil diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index c33b241766..ebf5712005 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -24,7 +24,7 @@ var quicConfig = &quic.Config{ MaxIncomingUniStreams: -1, // disable unidirectional streams MaxReceiveStreamFlowControlWindow: 3 * (1 << 20), // 3 MB MaxReceiveConnectionFlowControlWindow: 4.5 * (1 << 20), // 4.5 MB - AcceptCookie: func(clientAddr net.Addr, cookie *quic.Cookie) bool { + AcceptToken: func(clientAddr net.Addr, _ *quic.Token) bool { // TODO(#6): require source address validation when under load return true }, From cacd2a4016b0146c246902164f316a9cf115ddcd Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 29 Jul 2019 08:26:25 +0700 Subject: [PATCH 052/138] use the handshake logic from go-libp2p-tls --- p2p/transport/quic/conn_test.go | 79 +++---------- p2p/transport/quic/crypto.go | 123 -------------------- p2p/transport/quic/libp2pquic_suite_test.go | 6 + p2p/transport/quic/listener.go | 22 +++- p2p/transport/quic/listener_test.go | 1 + p2p/transport/quic/transport.go | 45 +++---- 6 files changed, 57 insertions(+), 219 deletions(-) delete mode 100644 p2p/transport/quic/crypto.go diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 8b86af2073..ef2430783b 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -4,10 +4,9 @@ import ( "bytes" "context" "crypto/rand" - "crypto/rsa" - "crypto/tls" - "crypto/x509" + "fmt" "io/ioutil" + mrand "math/rand" "time" ic "github.com/libp2p/go-libp2p-core/crypto" @@ -26,12 +25,26 @@ var _ = Describe("Connection", func() { ) createPeer := func() (peer.ID, ic.PrivKey) { - key, err := rsa.GenerateKey(rand.Reader, 1024) - Expect(err).ToNot(HaveOccurred()) - priv, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(key)) + var priv ic.PrivKey + var err error + switch mrand.Int() % 4 { + case 0: + fmt.Fprintf(GinkgoWriter, " using an ECDSA key: ") + priv, _, err = ic.GenerateECDSAKeyPair(rand.Reader) + case 1: + fmt.Fprintf(GinkgoWriter, " using an RSA key: ") + priv, _, err = ic.GenerateRSAKeyPair(1024, rand.Reader) + case 2: + fmt.Fprintf(GinkgoWriter, " using an Ed25519 key: ") + priv, _, err = ic.GenerateEd25519Key(rand.Reader) + case 3: + fmt.Fprintf(GinkgoWriter, " using an secp256k1 key: ") + priv, _, err = ic.GenerateSecp256k1Key(rand.Reader) + } Expect(err).ToNot(HaveOccurred()) id, err := peer.IDFromPrivateKey(priv) Expect(err).ToNot(HaveOccurred()) + fmt.Fprintln(GinkgoWriter, id.Pretty()) return id, priv } @@ -52,11 +65,6 @@ var _ = Describe("Connection", func() { return <-addrChan, connChan } - // modify the cert chain such that verificiation will fail - invalidateCertChain := func(tlsConf *tls.Config) { - tlsConf.Certificates[0].Certificate = [][]byte{tlsConf.Certificates[0].Certificate[0]} - } - BeforeEach(func() { serverID, serverKey = createPeer() clientID, clientKey = createPeer() @@ -141,55 +149,6 @@ var _ = Describe("Connection", func() { Consistently(serverConnChan).ShouldNot(Receive()) }) - It("fails if the client presents an invalid cert chain", func() { - serverTransport, err := NewTransport(serverKey) - Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") - - clientTransport, err := NewTransport(clientKey) - invalidateCertChain(clientTransport.(*transport).tlsConf) - Expect(err).ToNot(HaveOccurred()) - conn, err := clientTransport.Dial(context.Background(), serverAddr, serverID) - Expect(err).ToNot(HaveOccurred()) - Eventually(func() bool { return conn.IsClosed() }).Should(BeTrue()) - Consistently(serverConnChan).ShouldNot(Receive()) - }) - - It("fails if the server presents an invalid cert chain", func() { - serverTransport, err := NewTransport(serverKey) - invalidateCertChain(serverTransport.(*transport).tlsConf) - Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") - - clientTransport, err := NewTransport(clientKey) - Expect(err).ToNot(HaveOccurred()) - _, err = clientTransport.Dial(context.Background(), serverAddr, serverID) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("CRYPTO_ERROR")) - Consistently(serverConnChan).ShouldNot(Receive()) - }) - - It("keeps accepting connections after a failed connection attempt", func() { - serverTransport, err := NewTransport(serverKey) - Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") - - // first dial with an invalid cert chain - clientTransport1, err := NewTransport(clientKey) - invalidateCertChain(clientTransport1.(*transport).tlsConf) - Expect(err).ToNot(HaveOccurred()) - _, err = clientTransport1.Dial(context.Background(), serverAddr, serverID) - Expect(err).ToNot(HaveOccurred()) - Consistently(serverConnChan).ShouldNot(Receive()) - - // then dial with a valid client - clientTransport2, err := NewTransport(clientKey) - Expect(err).ToNot(HaveOccurred()) - _, err = clientTransport2.Dial(context.Background(), serverAddr, serverID) - Expect(err).ToNot(HaveOccurred()) - Eventually(serverConnChan).Should(Receive()) - }) - It("dials to two servers at the same time", func() { serverID2, serverKey2 := createPeer() diff --git a/p2p/transport/quic/crypto.go b/p2p/transport/quic/crypto.go deleted file mode 100644 index 2ce698680f..0000000000 --- a/p2p/transport/quic/crypto.go +++ /dev/null @@ -1,123 +0,0 @@ -package libp2pquic - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/tls" - "crypto/x509" - "errors" - "math/big" - "time" - - "github.com/gogo/protobuf/proto" - ic "github.com/libp2p/go-libp2p-core/crypto" - pb "github.com/libp2p/go-libp2p-core/crypto/pb" -) - -// mint certificate selection is broken. -const hostname = "quic.ipfs" - -const alpn string = "libp2p" - -const certValidityPeriod = 180 * 24 * time.Hour - -func generateConfig(privKey ic.PrivKey) (*tls.Config, error) { - key, hostCert, err := keyToCertificate(privKey) - if err != nil { - return nil, err - } - // The ephemeral key used just for a couple of connections (or a limited time). - ephemeralKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return nil, err - } - // Sign the ephemeral key using the host key. - // This is the only time that the host's private key of the peer is needed. - // Note that this step could be done asynchronously, such that a running node doesn't need access its private key at all. - certTemplate := &x509.Certificate{ - DNSNames: []string{hostname}, - SerialNumber: big.NewInt(1), - NotBefore: time.Now().Add(-24 * time.Hour), - NotAfter: time.Now().Add(certValidityPeriod), - } - certDER, err := x509.CreateCertificate(rand.Reader, certTemplate, hostCert, ephemeralKey.Public(), key) - if err != nil { - return nil, err - } - cert, err := x509.ParseCertificate(certDER) - if err != nil { - return nil, err - } - return &tls.Config{ - ServerName: hostname, - InsecureSkipVerify: true, // This is not insecure here. We will verify the cert chain ourselves. - ClientAuth: tls.RequireAnyClientCert, - Certificates: []tls.Certificate{{ - Certificate: [][]byte{cert.Raw, hostCert.Raw}, - PrivateKey: ephemeralKey, - }}, - NextProtos: []string{alpn}, - }, nil -} - -func getRemotePubKey(chain []*x509.Certificate) (ic.PubKey, error) { - if len(chain) != 2 { - return nil, errors.New("expected 2 certificates in the chain") - } - pool := x509.NewCertPool() - pool.AddCert(chain[1]) - if _, err := chain[0].Verify(x509.VerifyOptions{Roots: pool}); err != nil { - return nil, err - } - remotePubKey, err := x509.MarshalPKIXPublicKey(chain[1].PublicKey) - if err != nil { - return nil, err - } - return ic.UnmarshalRsaPublicKey(remotePubKey) -} - -func keyToCertificate(sk ic.PrivKey) (interface{}, *x509.Certificate, error) { - sn, err := rand.Int(rand.Reader, big.NewInt(1<<62)) - if err != nil { - return nil, nil, err - } - tmpl := &x509.Certificate{ - SerialNumber: sn, - NotBefore: time.Now().Add(-24 * time.Hour), - NotAfter: time.Now().Add(certValidityPeriod), - IsCA: true, - BasicConstraintsValid: true, - } - - var publicKey, privateKey interface{} - keyBytes, err := sk.Bytes() - if err != nil { - return nil, nil, err - } - pbmes := new(pb.PrivateKey) - if err := proto.Unmarshal(keyBytes, pbmes); err != nil { - return nil, nil, err - } - switch pbmes.GetType() { - case pb.KeyType_RSA: - k, err := x509.ParsePKCS1PrivateKey(pbmes.GetData()) - if err != nil { - return nil, nil, err - } - publicKey = &k.PublicKey - privateKey = k - // TODO: add support for ECDSA - default: - return nil, nil, errors.New("unsupported key type for TLS") - } - certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, publicKey, privateKey) - if err != nil { - return nil, nil, err - } - cert, err := x509.ParseCertificate(certDER) - if err != nil { - return nil, nil, err - } - return privateKey, cert, nil -} diff --git a/p2p/transport/quic/libp2pquic_suite_test.go b/p2p/transport/quic/libp2pquic_suite_test.go index 9675f74995..2bb5d416c6 100644 --- a/p2p/transport/quic/libp2pquic_suite_test.go +++ b/p2p/transport/quic/libp2pquic_suite_test.go @@ -1,6 +1,8 @@ package libp2pquic import ( + mrand "math/rand" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -11,3 +13,7 @@ func TestLibp2pQuicTransport(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "libp2p QUIC Transport Suite") } + +var _ = BeforeSuite(func() { + mrand.Seed(GinkgoRandomSeed()) +}) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 2e39854dd2..0d1b835326 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -8,14 +8,13 @@ import ( ic "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/peer" tpt "github.com/libp2p/go-libp2p-core/transport" + p2ptls "github.com/libp2p/go-libp2p-tls" quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" ) -var quicListenAddr = quic.ListenAddr - // A listener listens for QUIC connections. type listener struct { quicListener quic.Listener @@ -28,7 +27,16 @@ type listener struct { var _ tpt.Listener = &listener{} -func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, key ic.PrivKey, tlsConf *tls.Config) (tpt.Listener, error) { +func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, key ic.PrivKey, identity *p2ptls.Identity) (tpt.Listener, error) { + var tlsConf tls.Config + tlsConf.GetConfigForClient = func(_ *tls.ClientHelloInfo) (*tls.Config, error) { + // return a tls.Config that verifies the peer's certificate chain. + // Note that since we have no way of associating an incoming QUIC connection with + // the peer ID calculated here, we don't actually receive the peer's public key + // from the key chan. + conf, _ := identity.ConfigForAny() + return conf, nil + } lnet, host, err := manet.DialArgs(addr) if err != nil { return nil, err @@ -41,7 +49,7 @@ func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, if err != nil { return nil, err } - ln, err := quic.Listen(conn, tlsConf, quicConfig) + ln, err := quic.Listen(conn, &tlsConf, quicConfig) if err != nil { return nil, err } @@ -75,7 +83,11 @@ func (l *listener) Accept() (tpt.CapableConn, error) { } func (l *listener) setupConn(sess quic.Session) (tpt.CapableConn, error) { - remotePubKey, err := getRemotePubKey(sess.ConnectionState().PeerCertificates) + // The tls.Config used to establish this connection already verified the certificate chain. + // Since we don't have any way of knowing which tls.Config was used though, + // we have to re-determine the peer's identity here. + // Therefore, this is expected to never fail. + remotePubKey, err := p2ptls.PubKeyFromCertChain(sess.ConnectionState().PeerCertificates) if err != nil { return nil, err } diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index bf0435f1ac..44d1c4ab17 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -30,6 +30,7 @@ var _ = Describe("Listener", func() { Context("listening on the right address", func() { It("returns the address it is listening on", func() { localAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + Expect(err).ToNot(HaveOccurred()) ln, err := t.Listen(localAddr) Expect(err).ToNot(HaveOccurred()) netAddr := ln.Addr() diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index ebf5712005..e6fc143e2a 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -2,9 +2,6 @@ package libp2pquic import ( "context" - "crypto/tls" - "crypto/x509" - "errors" "fmt" "net" "sync" @@ -12,6 +9,7 @@ import ( ic "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/peer" tpt "github.com/libp2p/go-libp2p-core/transport" + p2ptls "github.com/libp2p/go-libp2p-tls" quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" @@ -74,7 +72,7 @@ func (c *connManager) createConn(network, host string) (net.PacketConn, error) { type transport struct { privKey ic.PrivKey localPeer peer.ID - tlsConf *tls.Config + identity *p2ptls.Identity connManager *connManager } @@ -86,7 +84,7 @@ func NewTransport(key ic.PrivKey) (tpt.Transport, error) { if err != nil { return nil, err } - tlsConf, err := generateConfig(key) + identity, err := p2ptls.NewIdentity(key) if err != nil { return nil, err } @@ -94,7 +92,7 @@ func NewTransport(key ic.PrivKey) (tpt.Transport, error) { return &transport{ privKey: key, localPeer: localPeer, - tlsConf: tlsConf, + identity: identity, connManager: &connManager{}, }, nil } @@ -113,30 +111,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if err != nil { return nil, err } - var remotePubKey ic.PubKey - tlsConf := t.tlsConf.Clone() - // We need to check the peer ID in the VerifyPeerCertificate callback. - // The tls.Config it is also used for listening, and we might also have concurrent dials. - // Clone it so we can check for the specific peer ID we're dialing here. - tlsConf.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error { - chain := make([]*x509.Certificate, len(rawCerts)) - for i := 0; i < len(rawCerts); i++ { - cert, err := x509.ParseCertificate(rawCerts[i]) - if err != nil { - return err - } - chain[i] = cert - } - var err error - remotePubKey, err = getRemotePubKey(chain) - if err != nil { - return err - } - if !p.MatchesPublicKey(remotePubKey) { - return errors.New("peer IDs don't match") - } - return nil - } + tlsConf, keyCh := t.identity.ConfigForPeer(p) sess, err := quic.DialContext(ctx, pconn, addr, host, tlsConf, quicConfig) if err != nil { return nil, err @@ -145,6 +120,14 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if err != nil { return nil, err } + + // Should be ready by this point, don't block. + var remotePubKey ic.PubKey + select { + case remotePubKey = <-keyCh: + default: + } + return &conn{ sess: sess, transport: t, @@ -164,7 +147,7 @@ func (t *transport) CanDial(addr ma.Multiaddr) bool { // Listen listens for new QUIC connections on the passed multiaddr. func (t *transport) Listen(addr ma.Multiaddr) (tpt.Listener, error) { - return newListener(addr, t, t.localPeer, t.privKey, t.tlsConf) + return newListener(addr, t, t.localPeer, t.privKey, t.identity) } // Proxy returns true if this transport proxies. From 5d493b46b352c6927077ddc21cdc8b2d316d89da Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 1 Aug 2019 09:20:19 +0700 Subject: [PATCH 053/138] add an error check that we actually received the peer's public key --- p2p/transport/quic/transport.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index e6fc143e2a..5b217087bb 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -2,6 +2,7 @@ package libp2pquic import ( "context" + "errors" "fmt" "net" "sync" @@ -127,6 +128,9 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp case remotePubKey = <-keyCh: default: } + if remotePubKey == nil { + return nil, errors.New("go-libp2p-quic-transport BUG: expected remote pub key to be set") + } return &conn{ sess: sess, From d1193da2f8ddcf39cff50104310b519090c06b36 Mon Sep 17 00:00:00 2001 From: lnykww Date: Thu, 21 Mar 2019 20:13:28 +0800 Subject: [PATCH 054/138] reuse listening connections for dialing --- p2p/transport/quic/listener.go | 27 ++---- p2p/transport/quic/reuse.go | 137 +++++++++++++++++++++++++++++++ p2p/transport/quic/reuse_test.go | 79 ++++++++++++++++++ p2p/transport/quic/transport.go | 82 +++++++++++------- 4 files changed, 275 insertions(+), 50 deletions(-) create mode 100644 p2p/transport/quic/reuse.go create mode 100644 p2p/transport/quic/reuse_test.go diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 0d1b835326..acc76f1b86 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -12,14 +12,13 @@ import ( quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr-net" ) // A listener listens for QUIC connections. type listener struct { - quicListener quic.Listener - transport tpt.Transport - + quicListener quic.Listener + conn *reuseConn + transport *transport privKey ic.PrivKey localPeer peer.ID localMultiaddr ma.Multiaddr @@ -27,7 +26,7 @@ type listener struct { var _ tpt.Listener = &listener{} -func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, key ic.PrivKey, identity *p2ptls.Identity) (tpt.Listener, error) { +func newListener(rconn *reuseConn, t *transport, localPeer peer.ID, key ic.PrivKey, identity *p2ptls.Identity) (tpt.Listener, error) { var tlsConf tls.Config tlsConf.GetConfigForClient = func(_ *tls.ClientHelloInfo) (*tls.Config, error) { // return a tls.Config that verifies the peer's certificate chain. @@ -37,19 +36,7 @@ func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, conf, _ := identity.ConfigForAny() return conf, nil } - lnet, host, err := manet.DialArgs(addr) - if err != nil { - return nil, err - } - laddr, err := net.ResolveUDPAddr(lnet, host) - if err != nil { - return nil, err - } - conn, err := net.ListenUDP(lnet, laddr) - if err != nil { - return nil, err - } - ln, err := quic.Listen(conn, &tlsConf, quicConfig) + ln, err := quic.Listen(rconn, &tlsConf, quicConfig) if err != nil { return nil, err } @@ -58,8 +45,9 @@ func newListener(addr ma.Multiaddr, transport tpt.Transport, localPeer peer.ID, return nil, err } return &listener{ + conn: rconn, quicListener: ln, - transport: transport, + transport: t, privKey: key, localPeer: localPeer, localMultiaddr: localMultiaddr, @@ -113,6 +101,7 @@ func (l *listener) setupConn(sess quic.Session) (tpt.CapableConn, error) { // Close closes the listener. func (l *listener) Close() error { + l.conn.DecreaseCount() return l.quicListener.Close() } diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go new file mode 100644 index 0000000000..56aba1b491 --- /dev/null +++ b/p2p/transport/quic/reuse.go @@ -0,0 +1,137 @@ +package libp2pquic + +import ( + "net" + "sync" + "sync/atomic" + + "github.com/vishvananda/netlink" +) + +type reuseConn struct { + net.PacketConn + refCount int32 // to be used as an atomic +} + +func newReuseConn(conn net.PacketConn) *reuseConn { + return &reuseConn{PacketConn: conn} +} + +func (c *reuseConn) IncreaseCount() { atomic.AddInt32(&c.refCount, 1) } +func (c *reuseConn) DecreaseCount() { atomic.AddInt32(&c.refCount, -1) } +func (c *reuseConn) GetCount() int { return int(atomic.LoadInt32(&c.refCount)) } + +type reuse struct { + mutex sync.Mutex + + unicast map[string] /* IP.String() */ map[int] /* port */ *reuseConn + // global contains connections that are listening on 0.0.0.0 / :: + global map[int]*reuseConn +} + +func newReuse() *reuse { + return &reuse{ + unicast: make(map[string]map[int]*reuseConn), + global: make(map[int]*reuseConn), + } +} + +func (r *reuse) getSourceIPs(network string, raddr *net.UDPAddr) ([]net.IP, error) { + // Determine the source address that the kernel would use for this IP address. + // Note: This only works on Linux. + // On other OSes, this will return a netlink.ErrNotImplemetned. + routes, err := (&netlink.Handle{}).RouteGet(raddr.IP) + if err != nil { + return nil, err + } + + ips := make([]net.IP, 0, len(routes)) + for _, route := range routes { + ips = append(ips, route.Src) + } + return ips, nil +} + +func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { + ips, err := r.getSourceIPs(network, raddr) + if err != nil && err != netlink.ErrNotImplemented { + return nil, err + } + + r.mutex.Lock() + defer r.mutex.Unlock() + + conn, err := r.dialLocked(network, raddr, ips) + if err != nil { + return nil, err + } + conn.IncreaseCount() + return conn, nil +} + +func (r *reuse) dialLocked(network string, raddr *net.UDPAddr, ips []net.IP) (*reuseConn, error) { + for _, ip := range ips { + // We already have at least one suitable connection... + if conns, ok := r.unicast[ip.String()]; ok { + // ... we don't care which port we're dialing from. Just use the first. + for _, c := range conns { + return c, nil + } + } + } + + // Use a connection listening on 0.0.0.0 (or ::). + // Again, we don't care about the port number. + for _, conn := range r.global { + return conn, nil + } + + // We don't have a connection that we can use for dialing. + // Dial a new connection from a random port. + var addr *net.UDPAddr + switch network { + case "udp4": + addr = &net.UDPAddr{IP: net.IPv4zero, Port: 0} + case "udp6": + addr = &net.UDPAddr{IP: net.IPv6zero, Port: 0} + } + conn, err := net.ListenUDP(network, addr) + if err != nil { + return nil, err + } + rconn := newReuseConn(conn) + r.global[conn.LocalAddr().(*net.UDPAddr).Port] = rconn + return rconn, nil +} + +func (r *reuse) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { + conn, err := net.ListenUDP(network, laddr) + if err != nil { + return nil, err + } + localAddr := conn.LocalAddr().(*net.UDPAddr) + + rconn := newReuseConn(conn) + rconn.IncreaseCount() + + r.mutex.Lock() + defer r.mutex.Unlock() + + // Deal with listen on a global address + if laddr.IP.IsUnspecified() { + // The kernel already checked that the laddr is not already listen + // so we need not check here (when we create ListenUDP). + r.global[laddr.Port] = rconn + return rconn, err + } + + // Deal with listen on a unicast address + if _, ok := r.unicast[localAddr.IP.String()]; !ok { + r.unicast[laddr.IP.String()] = make(map[int]*reuseConn) + } + + // The kernel already checked that the laddr is not already listen + // so we need not check here (when we create ListenUDP). + r.unicast[laddr.IP.String()][localAddr.Port] = rconn + return rconn, err +} diff --git a/p2p/transport/quic/reuse_test.go b/p2p/transport/quic/reuse_test.go new file mode 100644 index 0000000000..2259a39195 --- /dev/null +++ b/p2p/transport/quic/reuse_test.go @@ -0,0 +1,79 @@ +package libp2pquic + +import ( + "net" + "runtime" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Reuse", func() { + var reuse *reuse + + BeforeEach(func() { + reuse = newReuse() + }) + + It("creates a new global connection when listening on 0.0.0.0", func() { + addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") + Expect(err).ToNot(HaveOccurred()) + conn, err := reuse.Listen("udp4", addr) + Expect(err).ToNot(HaveOccurred()) + Expect(conn.GetCount()).To(Equal(1)) + }) + + It("creates a new global connection when listening on [::]", func() { + addr, err := net.ResolveUDPAddr("udp6", "[::]:1234") + Expect(err).ToNot(HaveOccurred()) + conn, err := reuse.Listen("udp6", addr) + Expect(err).ToNot(HaveOccurred()) + Expect(conn.GetCount()).To(Equal(1)) + }) + + It("creates a new global connection when dialing", func() { + addr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") + Expect(err).ToNot(HaveOccurred()) + conn, err := reuse.Dial("udp4", addr) + Expect(err).ToNot(HaveOccurred()) + Expect(conn.GetCount()).To(Equal(1)) + laddr := conn.LocalAddr().(*net.UDPAddr) + Expect(laddr.IP.String()).To(Equal("0.0.0.0")) + Expect(laddr.Port).ToNot(BeZero()) + }) + + It("reuses a connection it created for listening when dialing", func() { + // listen + addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") + Expect(err).ToNot(HaveOccurred()) + lconn, err := reuse.Listen("udp4", addr) + Expect(err).ToNot(HaveOccurred()) + Expect(lconn.GetCount()).To(Equal(1)) + // dial + raddr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") + Expect(err).ToNot(HaveOccurred()) + conn, err := reuse.Dial("udp4", raddr) + Expect(err).ToNot(HaveOccurred()) + Expect(conn.GetCount()).To(Equal(2)) + }) + + if runtime.GOOS == "linux" { + It("reuses a connection it created for listening on a specific interface", func() { + raddr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") + Expect(err).ToNot(HaveOccurred()) + ips, err := reuse.getSourceIPs("udp4", raddr) + Expect(err).ToNot(HaveOccurred()) + Expect(ips).ToNot(BeEmpty()) + // listen + addr, err := net.ResolveUDPAddr("udp4", ips[0].String()+":0") + Expect(err).ToNot(HaveOccurred()) + lconn, err := reuse.Listen("udp4", addr) + Expect(err).ToNot(HaveOccurred()) + Expect(lconn.GetCount()).To(Equal(1)) + // dial + conn, err := reuse.Dial("udp4", raddr) + Expect(err).ToNot(HaveOccurred()) + Expect(conn.GetCount()).To(Equal(2)) + }) + } +}) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 5b217087bb..bd0adfc714 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -3,9 +3,7 @@ package libp2pquic import ( "context" "errors" - "fmt" "net" - "sync" ic "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/peer" @@ -31,42 +29,42 @@ var quicConfig = &quic.Config{ } type connManager struct { - mutex sync.Mutex - - connIPv4 net.PacketConn - connIPv6 net.PacketConn + reuseUDP4 *reuse + reuseUDP6 *reuse } -func (c *connManager) GetConnForAddr(network string) (net.PacketConn, error) { - c.mutex.Lock() - defer c.mutex.Unlock() +func newConnManager() *connManager { + return &connManager{ + reuseUDP4: newReuse(), + reuseUDP6: newReuse(), + } +} +func (c *connManager) getReuse(network string) (*reuse, error) { switch network { case "udp4": - if c.connIPv4 != nil { - return c.connIPv4, nil - } - var err error - c.connIPv4, err = c.createConn(network, "0.0.0.0:0") - return c.connIPv4, err + return c.reuseUDP4, nil case "udp6": - if c.connIPv6 != nil { - return c.connIPv6, nil - } - var err error - c.connIPv6, err = c.createConn(network, ":0") - return c.connIPv6, err + return c.reuseUDP6, nil default: - return nil, fmt.Errorf("unsupported network: %s", network) + return nil, errors.New("invalid network: must be either udp4 or udp6") + } +} + +func (c *connManager) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { + reuse, err := c.getReuse(network) + if err != nil { + return nil, err } + return reuse.Listen(network, laddr) } -func (c *connManager) createConn(network, host string) (net.PacketConn, error) { - addr, err := net.ResolveUDPAddr(network, host) +func (c *connManager) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { + reuse, err := c.getReuse(network) if err != nil { return nil, err } - return net.ListenUDP(network, addr) + return reuse.Dial(network, raddr) } // The Transport implements the tpt.Transport interface for QUIC connections. @@ -94,7 +92,7 @@ func NewTransport(key ic.PrivKey) (tpt.Transport, error) { privKey: key, localPeer: localPeer, identity: identity, - connManager: &connManager{}, + connManager: newConnManager(), }, nil } @@ -104,7 +102,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if err != nil { return nil, err } - pconn, err := t.connManager.GetConnForAddr(network) + udpAddr, err := net.ResolveUDPAddr(network, host) if err != nil { return nil, err } @@ -113,15 +111,15 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp return nil, err } tlsConf, keyCh := t.identity.ConfigForPeer(p) - sess, err := quic.DialContext(ctx, pconn, addr, host, tlsConf, quicConfig) + pconn, err := t.connManager.Dial(network, udpAddr) if err != nil { return nil, err } - localMultiaddr, err := toQuicMultiaddr(sess.LocalAddr()) + sess, err := quic.DialContext(ctx, pconn, addr, host, tlsConf, quicConfig) if err != nil { + pconn.DecreaseCount() return nil, err } - // Should be ready by this point, don't block. var remotePubKey ic.PubKey select { @@ -129,9 +127,19 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp default: } if remotePubKey == nil { + pconn.DecreaseCount() return nil, errors.New("go-libp2p-quic-transport BUG: expected remote pub key to be set") } + go func() { + <-sess.Context().Done() + pconn.DecreaseCount() + }() + localMultiaddr, err := toQuicMultiaddr(pconn.LocalAddr()) + if err != nil { + pconn.DecreaseCount() + return nil, err + } return &conn{ sess: sess, transport: t, @@ -151,7 +159,19 @@ func (t *transport) CanDial(addr ma.Multiaddr) bool { // Listen listens for new QUIC connections on the passed multiaddr. func (t *transport) Listen(addr ma.Multiaddr) (tpt.Listener, error) { - return newListener(addr, t, t.localPeer, t.privKey, t.identity) + lnet, host, err := manet.DialArgs(addr) + if err != nil { + return nil, err + } + laddr, err := net.ResolveUDPAddr(lnet, host) + if err != nil { + return nil, err + } + conn, err := t.connManager.Listen(lnet, laddr) + if err != nil { + return nil, err + } + return newListener(conn, t, t.localPeer, t.privKey, t.identity) } // Proxy returns true if this transport proxies. From 46775f0070554d7619a87ca3d6bdf75e91a4521d Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 5 Aug 2019 18:12:51 +0700 Subject: [PATCH 055/138] use a single handle for each reuse --- p2p/transport/quic/reuse.go | 28 +++++++++++++++++++++------- p2p/transport/quic/reuse_test.go | 4 +++- p2p/transport/quic/transport.go | 22 +++++++++++++++++----- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go index 56aba1b491..08fc14f3d7 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse.go @@ -24,23 +24,37 @@ func (c *reuseConn) GetCount() int { return int(atomic.LoadInt32(&c.refCount)) type reuse struct { mutex sync.Mutex + handle *netlink.Handle // Only set on Linux. nil on other systems. + unicast map[string] /* IP.String() */ map[int] /* port */ *reuseConn // global contains connections that are listening on 0.0.0.0 / :: global map[int]*reuseConn } -func newReuse() *reuse { +func newReuse() (*reuse, error) { + // On non-Linux systems, this will return ErrNotImplemented. + handle, err := netlink.NewHandle() + if err == netlink.ErrNotImplemented { + handle = nil + } else if err != nil { + return nil, err + } return &reuse{ unicast: make(map[string]map[int]*reuseConn), global: make(map[int]*reuseConn), - } + handle: handle, + }, nil } +// Get the source IP that the kernel would use for dialing. +// This only works on Linux. +// On other systems, this returns an empty slice of IP addresses. func (r *reuse) getSourceIPs(network string, raddr *net.UDPAddr) ([]net.IP, error) { - // Determine the source address that the kernel would use for this IP address. - // Note: This only works on Linux. - // On other OSes, this will return a netlink.ErrNotImplemetned. - routes, err := (&netlink.Handle{}).RouteGet(raddr.IP) + if r.handle == nil { + return nil, nil + } + + routes, err := r.handle.RouteGet(raddr.IP) if err != nil { return nil, err } @@ -54,7 +68,7 @@ func (r *reuse) getSourceIPs(network string, raddr *net.UDPAddr) ([]net.IP, erro func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { ips, err := r.getSourceIPs(network, raddr) - if err != nil && err != netlink.ErrNotImplemented { + if err != nil { return nil, err } diff --git a/p2p/transport/quic/reuse_test.go b/p2p/transport/quic/reuse_test.go index 2259a39195..91668b0f3b 100644 --- a/p2p/transport/quic/reuse_test.go +++ b/p2p/transport/quic/reuse_test.go @@ -12,7 +12,9 @@ var _ = Describe("Reuse", func() { var reuse *reuse BeforeEach(func() { - reuse = newReuse() + var err error + reuse, err = newReuse() + Expect(err).ToNot(HaveOccurred()) }) It("creates a new global connection when listening on 0.0.0.0", func() { diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index bd0adfc714..a9ddd41355 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -33,11 +33,19 @@ type connManager struct { reuseUDP6 *reuse } -func newConnManager() *connManager { - return &connManager{ - reuseUDP4: newReuse(), - reuseUDP6: newReuse(), +func newConnManager() (*connManager, error) { + reuseUDP4, err := newReuse() + if err != nil { + return nil, err + } + reuseUDP6, err := newReuse() + if err != nil { + return nil, err } + return &connManager{ + reuseUDP4: reuseUDP4, + reuseUDP6: reuseUDP6, + }, nil } func (c *connManager) getReuse(network string) (*reuse, error) { @@ -87,12 +95,16 @@ func NewTransport(key ic.PrivKey) (tpt.Transport, error) { if err != nil { return nil, err } + connManager, err := newConnManager() + if err != nil { + return nil, err + } return &transport{ privKey: key, localPeer: localPeer, identity: identity, - connManager: newConnManager(), + connManager: connManager, }, nil } From ff46be45996a42de47c009999874ddea0d0de6d5 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 6 Aug 2019 18:20:09 +0700 Subject: [PATCH 056/138] fix saving of listening connections when listening on IP:0 --- p2p/transport/quic/reuse.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go index 08fc14f3d7..47a1ffb2c9 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse.go @@ -132,20 +132,20 @@ func (r *reuse) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { defer r.mutex.Unlock() // Deal with listen on a global address - if laddr.IP.IsUnspecified() { + if localAddr.IP.IsUnspecified() { // The kernel already checked that the laddr is not already listen // so we need not check here (when we create ListenUDP). - r.global[laddr.Port] = rconn + r.global[localAddr.Port] = rconn return rconn, err } // Deal with listen on a unicast address if _, ok := r.unicast[localAddr.IP.String()]; !ok { - r.unicast[laddr.IP.String()] = make(map[int]*reuseConn) + r.unicast[localAddr.IP.String()] = make(map[int]*reuseConn) } // The kernel already checked that the laddr is not already listen // so we need not check here (when we create ListenUDP). - r.unicast[laddr.IP.String()][localAddr.Port] = rconn + r.unicast[localAddr.IP.String()][localAddr.Port] = rconn return rconn, err } From 19dcc590372622c3128d5f6424e88b2a7a7ec626 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 6 Aug 2019 18:48:51 +0700 Subject: [PATCH 057/138] delete reuse-connection if they aren't used for more than 10 seconds --- p2p/transport/quic/conn_test.go | 86 ++++++---- p2p/transport/quic/libp2pquic_suite_test.go | 27 ++++ p2p/transport/quic/listener.go | 2 +- p2p/transport/quic/listener_test.go | 3 + p2p/transport/quic/reuse.go | 86 +++++++++- p2p/transport/quic/reuse_test.go | 170 ++++++++++++++------ 6 files changed, 289 insertions(+), 85 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index ef2430783b..bf8d681149 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -48,21 +48,12 @@ var _ = Describe("Connection", func() { return id, priv } - runServer := func(tr tpt.Transport, multiaddr string) (ma.Multiaddr, <-chan tpt.CapableConn) { - addrChan := make(chan ma.Multiaddr) - connChan := make(chan tpt.CapableConn) - go func() { - defer GinkgoRecover() - addr, err := ma.NewMultiaddr(multiaddr) - Expect(err).ToNot(HaveOccurred()) - ln, err := tr.Listen(addr) - Expect(err).ToNot(HaveOccurred()) - addrChan <- ln.Multiaddr() - conn, err := ln.Accept() - Expect(err).ToNot(HaveOccurred()) - connChan <- conn - }() - return <-addrChan, connChan + runServer := func(tr tpt.Transport, multiaddr string) tpt.Listener { + addr, err := ma.NewMultiaddr(multiaddr) + Expect(err).ToNot(HaveOccurred()) + ln, err := tr.Listen(addr) + Expect(err).ToNot(HaveOccurred()) + return ln } BeforeEach(func() { @@ -73,13 +64,17 @@ var _ = Describe("Connection", func() { It("handshakes on IPv4", func() { serverTransport, err := NewTransport(serverKey) Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") + ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") + defer ln.Close() clientTransport, err := NewTransport(clientKey) Expect(err).ToNot(HaveOccurred()) - conn, err := clientTransport.Dial(context.Background(), serverAddr, serverID) + conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) + Expect(err).ToNot(HaveOccurred()) + defer conn.Close() + serverConn, err := ln.Accept() Expect(err).ToNot(HaveOccurred()) - serverConn := <-serverConnChan + defer serverConn.Close() Expect(conn.LocalPeer()).To(Equal(clientID)) Expect(conn.LocalPrivateKey()).To(Equal(clientKey)) Expect(conn.RemotePeer()).To(Equal(serverID)) @@ -93,13 +88,17 @@ var _ = Describe("Connection", func() { It("handshakes on IPv6", func() { serverTransport, err := NewTransport(serverKey) Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport, "/ip6/::1/udp/0/quic") + ln := runServer(serverTransport, "/ip6/::1/udp/0/quic") + defer ln.Close() clientTransport, err := NewTransport(clientKey) Expect(err).ToNot(HaveOccurred()) - conn, err := clientTransport.Dial(context.Background(), serverAddr, serverID) + conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) - serverConn := <-serverConnChan + defer conn.Close() + serverConn, err := ln.Accept() + Expect(err).ToNot(HaveOccurred()) + defer serverConn.Close() Expect(conn.LocalPeer()).To(Equal(clientID)) Expect(conn.LocalPrivateKey()).To(Equal(clientKey)) Expect(conn.RemotePeer()).To(Equal(serverID)) @@ -113,13 +112,17 @@ var _ = Describe("Connection", func() { It("opens and accepts streams", func() { serverTransport, err := NewTransport(serverKey) Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") + ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") + defer ln.Close() clientTransport, err := NewTransport(clientKey) Expect(err).ToNot(HaveOccurred()) - conn, err := clientTransport.Dial(context.Background(), serverAddr, serverID) + conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) + Expect(err).ToNot(HaveOccurred()) + defer conn.Close() + serverConn, err := ln.Accept() Expect(err).ToNot(HaveOccurred()) - serverConn := <-serverConnChan + defer serverConn.Close() str, err := conn.OpenStream() Expect(err).ToNot(HaveOccurred()) @@ -138,15 +141,24 @@ var _ = Describe("Connection", func() { serverTransport, err := NewTransport(serverKey) Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") + ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") clientTransport, err := NewTransport(clientKey) Expect(err).ToNot(HaveOccurred()) // dial, but expect the wrong peer ID - _, err = clientTransport.Dial(context.Background(), serverAddr, thirdPartyID) + _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), thirdPartyID) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("CRYPTO_ERROR")) - Consistently(serverConnChan).ShouldNot(Receive()) + + done := make(chan struct{}) + go func() { + defer GinkgoRecover() + defer close(done) + ln.Accept() + }() + Consistently(done).ShouldNot(BeClosed()) + ln.Close() + Eventually(done).Should(BeClosed()) }) It("dials to two servers at the same time", func() { @@ -154,16 +166,23 @@ var _ = Describe("Connection", func() { serverTransport, err := NewTransport(serverKey) Expect(err).ToNot(HaveOccurred()) - serverAddr, serverConnChan := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") + ln1 := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") serverTransport2, err := NewTransport(serverKey2) + defer ln1.Close() Expect(err).ToNot(HaveOccurred()) - serverAddr2, serverConnChan2 := runServer(serverTransport2, "/ip4/127.0.0.1/udp/0/quic") + ln2 := runServer(serverTransport2, "/ip4/127.0.0.1/udp/0/quic") + defer ln2.Close() data := bytes.Repeat([]byte{'a'}, 5*1<<20) // 5 MB // wait for both servers to accept a connection // then send some data go func() { - for _, c := range []tpt.CapableConn{<-serverConnChan, <-serverConnChan2} { + serverConn1, err := ln1.Accept() + Expect(err).ToNot(HaveOccurred()) + serverConn2, err := ln2.Accept() + Expect(err).ToNot(HaveOccurred()) + + for _, c := range []tpt.CapableConn{serverConn1, serverConn2} { go func(conn tpt.CapableConn) { defer GinkgoRecover() str, err := conn.OpenStream() @@ -177,10 +196,12 @@ var _ = Describe("Connection", func() { clientTransport, err := NewTransport(clientKey) Expect(err).ToNot(HaveOccurred()) - c1, err := clientTransport.Dial(context.Background(), serverAddr, serverID) + c1, err := clientTransport.Dial(context.Background(), ln1.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) - c2, err := clientTransport.Dial(context.Background(), serverAddr2, serverID2) + defer c1.Close() + c2, err := clientTransport.Dial(context.Background(), ln2.Multiaddr(), serverID2) Expect(err).ToNot(HaveOccurred()) + defer c2.Close() done := make(chan struct{}, 2) // receive the data on both connections at the same time @@ -193,7 +214,6 @@ var _ = Describe("Connection", func() { d, err := ioutil.ReadAll(str) Expect(err).ToNot(HaveOccurred()) Expect(d).To(Equal(data)) - conn.Close() done <- struct{}{} }(c) } diff --git a/p2p/transport/quic/libp2pquic_suite_test.go b/p2p/transport/quic/libp2pquic_suite_test.go index 2bb5d416c6..ce48c3f619 100644 --- a/p2p/transport/quic/libp2pquic_suite_test.go +++ b/p2p/transport/quic/libp2pquic_suite_test.go @@ -1,7 +1,11 @@ package libp2pquic import ( + "bytes" mrand "math/rand" + "runtime/pprof" + "strings" + "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -17,3 +21,26 @@ func TestLibp2pQuicTransport(t *testing.T) { var _ = BeforeSuite(func() { mrand.Seed(GinkgoRandomSeed()) }) + +var garbageCollectIntervalOrig time.Duration +var maxUnusedDurationOrig time.Duration + +func isGarbageCollectorRunning() bool { + var b bytes.Buffer + pprof.Lookup("goroutine").WriteTo(&b, 1) + return strings.Contains(b.String(), "go-libp2p-quic-transport.(*reuse).runGarbageCollector") +} + +var _ = BeforeEach(func() { + Expect(isGarbageCollectorRunning()).To(BeFalse()) + garbageCollectIntervalOrig = garbageCollectInterval + maxUnusedDurationOrig = maxUnusedDuration + garbageCollectInterval = 50 * time.Millisecond + maxUnusedDuration = 0 +}) + +var _ = AfterEach(func() { + Eventually(isGarbageCollectorRunning).Should(BeFalse()) + garbageCollectInterval = garbageCollectIntervalOrig + maxUnusedDuration = maxUnusedDurationOrig +}) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index acc76f1b86..ed2fb731c3 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -101,7 +101,7 @@ func (l *listener) setupConn(sess quic.Session) (tpt.CapableConn, error) { // Close closes the listener. func (l *listener) Close() error { - l.conn.DecreaseCount() + defer l.conn.DecreaseCount() return l.quicListener.Close() } diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index 44d1c4ab17..e90debc810 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -33,6 +33,7 @@ var _ = Describe("Listener", func() { Expect(err).ToNot(HaveOccurred()) ln, err := t.Listen(localAddr) Expect(err).ToNot(HaveOccurred()) + defer ln.Close() netAddr := ln.Addr() Expect(netAddr).To(BeAssignableToTypeOf(&net.UDPAddr{})) port := netAddr.(*net.UDPAddr).Port @@ -45,6 +46,7 @@ var _ = Describe("Listener", func() { Expect(err).ToNot(HaveOccurred()) ln, err := t.Listen(localAddr) Expect(err).ToNot(HaveOccurred()) + defer ln.Close() netAddr := ln.Addr() Expect(netAddr).To(BeAssignableToTypeOf(&net.UDPAddr{})) port := netAddr.(*net.UDPAddr).Port @@ -57,6 +59,7 @@ var _ = Describe("Listener", func() { Expect(err).ToNot(HaveOccurred()) ln, err := t.Listen(localAddr) Expect(err).ToNot(HaveOccurred()) + defer ln.Close() netAddr := ln.Addr() Expect(netAddr).To(BeAssignableToTypeOf(&net.UDPAddr{})) port := netAddr.(*net.UDPAddr).Port diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go index 47a1ffb2c9..2903b68384 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse.go @@ -3,27 +3,56 @@ package libp2pquic import ( "net" "sync" - "sync/atomic" + "time" "github.com/vishvananda/netlink" ) +// Constants. Defined as variables to simplify testing. +var ( + garbageCollectInterval = 30 * time.Second + maxUnusedDuration = 10 * time.Second +) + type reuseConn struct { net.PacketConn - refCount int32 // to be used as an atomic + + mutex sync.Mutex + refCount int + unusedSince time.Time } func newReuseConn(conn net.PacketConn) *reuseConn { return &reuseConn{PacketConn: conn} } -func (c *reuseConn) IncreaseCount() { atomic.AddInt32(&c.refCount, 1) } -func (c *reuseConn) DecreaseCount() { atomic.AddInt32(&c.refCount, -1) } -func (c *reuseConn) GetCount() int { return int(atomic.LoadInt32(&c.refCount)) } +func (c *reuseConn) IncreaseCount() { + c.mutex.Lock() + c.refCount++ + c.unusedSince = time.Time{} + c.mutex.Unlock() +} + +func (c *reuseConn) DecreaseCount() { + c.mutex.Lock() + c.refCount-- + if c.refCount == 0 { + c.unusedSince = time.Now() + } + c.mutex.Unlock() +} + +func (c *reuseConn) ShouldGarbageCollect(now time.Time) bool { + c.mutex.Lock() + defer c.mutex.Unlock() + return !c.unusedSince.IsZero() && c.unusedSince.Add(maxUnusedDuration).Before(now) +} type reuse struct { mutex sync.Mutex + garbageCollectorRunning bool + handle *netlink.Handle // Only set on Linux. nil on other systems. unicast map[string] /* IP.String() */ map[int] /* port */ *reuseConn @@ -46,6 +75,50 @@ func newReuse() (*reuse, error) { }, nil } +func (r *reuse) runGarbageCollector() { + ticker := time.NewTicker(garbageCollectInterval) + defer ticker.Stop() + + for now := range ticker.C { + var shouldExit bool + r.mutex.Lock() + for key, conn := range r.global { + if conn.ShouldGarbageCollect(now) { + delete(r.global, key) + } + } + for ukey, conns := range r.unicast { + for key, conn := range conns { + if conn.ShouldGarbageCollect(now) { + delete(conns, key) + } + } + if len(conns) == 0 { + delete(r.unicast, ukey) + } + } + + // stop the garbage collector if we're not tracking any connections + if len(r.global) == 0 && len(r.unicast) == 0 { + r.garbageCollectorRunning = false + shouldExit = true + } + r.mutex.Unlock() + + if shouldExit { + return + } + } +} + +// must be called while holding the mutex +func (r *reuse) maybeStartGarbageCollector() { + if !r.garbageCollectorRunning { + r.garbageCollectorRunning = true + go r.runGarbageCollector() + } +} + // Get the source IP that the kernel would use for dialing. // This only works on Linux. // On other systems, this returns an empty slice of IP addresses. @@ -80,6 +153,7 @@ func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { return nil, err } conn.IncreaseCount() + r.maybeStartGarbageCollector() return conn, nil } @@ -131,6 +205,8 @@ func (r *reuse) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { r.mutex.Lock() defer r.mutex.Unlock() + r.maybeStartGarbageCollector() + // Deal with listen on a global address if localAddr.IP.IsUnspecified() { // The kernel already checked that the laddr is not already listen diff --git a/p2p/transport/quic/reuse_test.go b/p2p/transport/quic/reuse_test.go index 91668b0f3b..276fd5bd95 100644 --- a/p2p/transport/quic/reuse_test.go +++ b/p2p/transport/quic/reuse_test.go @@ -3,11 +3,18 @@ package libp2pquic import ( "net" "runtime" + "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) +func (c *reuseConn) GetCount() int { + c.mutex.Lock() + defer c.mutex.Unlock() + return c.refCount +} + var _ = Describe("Reuse", func() { var reuse *reuse @@ -17,65 +24,136 @@ var _ = Describe("Reuse", func() { Expect(err).ToNot(HaveOccurred()) }) - It("creates a new global connection when listening on 0.0.0.0", func() { - addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") - Expect(err).ToNot(HaveOccurred()) - conn, err := reuse.Listen("udp4", addr) - Expect(err).ToNot(HaveOccurred()) - Expect(conn.GetCount()).To(Equal(1)) - }) - - It("creates a new global connection when listening on [::]", func() { - addr, err := net.ResolveUDPAddr("udp6", "[::]:1234") - Expect(err).ToNot(HaveOccurred()) - conn, err := reuse.Listen("udp6", addr) - Expect(err).ToNot(HaveOccurred()) - Expect(conn.GetCount()).To(Equal(1)) - }) + Context("creating and reusing connections", func() { + AfterEach(func() { + reuse.mutex.Lock() + for _, conn := range reuse.global { + for conn.GetCount() > 0 { + conn.DecreaseCount() + } + } + for _, conns := range reuse.unicast { + for _, conn := range conns { + for conn.GetCount() > 0 { + conn.DecreaseCount() + } + } + } + reuse.mutex.Unlock() + Eventually(isGarbageCollectorRunning).Should(BeFalse()) + }) - It("creates a new global connection when dialing", func() { - addr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") - Expect(err).ToNot(HaveOccurred()) - conn, err := reuse.Dial("udp4", addr) - Expect(err).ToNot(HaveOccurred()) - Expect(conn.GetCount()).To(Equal(1)) - laddr := conn.LocalAddr().(*net.UDPAddr) - Expect(laddr.IP.String()).To(Equal("0.0.0.0")) - Expect(laddr.Port).ToNot(BeZero()) - }) + It("creates a new global connection when listening on 0.0.0.0", func() { + addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") + Expect(err).ToNot(HaveOccurred()) + conn, err := reuse.Listen("udp4", addr) + Expect(err).ToNot(HaveOccurred()) + Expect(conn.GetCount()).To(Equal(1)) + }) - It("reuses a connection it created for listening when dialing", func() { - // listen - addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") - Expect(err).ToNot(HaveOccurred()) - lconn, err := reuse.Listen("udp4", addr) - Expect(err).ToNot(HaveOccurred()) - Expect(lconn.GetCount()).To(Equal(1)) - // dial - raddr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") - Expect(err).ToNot(HaveOccurred()) - conn, err := reuse.Dial("udp4", raddr) - Expect(err).ToNot(HaveOccurred()) - Expect(conn.GetCount()).To(Equal(2)) - }) + It("creates a new global connection when listening on [::]", func() { + addr, err := net.ResolveUDPAddr("udp6", "[::]:1234") + Expect(err).ToNot(HaveOccurred()) + conn, err := reuse.Listen("udp6", addr) + Expect(err).ToNot(HaveOccurred()) + Expect(conn.GetCount()).To(Equal(1)) + }) - if runtime.GOOS == "linux" { - It("reuses a connection it created for listening on a specific interface", func() { - raddr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") + It("creates a new global connection when dialing", func() { + addr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") Expect(err).ToNot(HaveOccurred()) - ips, err := reuse.getSourceIPs("udp4", raddr) + conn, err := reuse.Dial("udp4", addr) Expect(err).ToNot(HaveOccurred()) - Expect(ips).ToNot(BeEmpty()) + Expect(conn.GetCount()).To(Equal(1)) + laddr := conn.LocalAddr().(*net.UDPAddr) + Expect(laddr.IP.String()).To(Equal("0.0.0.0")) + Expect(laddr.Port).ToNot(BeZero()) + }) + + It("reuses a connection it created for listening when dialing", func() { // listen - addr, err := net.ResolveUDPAddr("udp4", ips[0].String()+":0") + addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") Expect(err).ToNot(HaveOccurred()) lconn, err := reuse.Listen("udp4", addr) Expect(err).ToNot(HaveOccurred()) Expect(lconn.GetCount()).To(Equal(1)) // dial + raddr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") + Expect(err).ToNot(HaveOccurred()) conn, err := reuse.Dial("udp4", raddr) Expect(err).ToNot(HaveOccurred()) Expect(conn.GetCount()).To(Equal(2)) }) - } + + if runtime.GOOS == "linux" { + It("reuses a connection it created for listening on a specific interface", func() { + raddr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") + Expect(err).ToNot(HaveOccurred()) + ips, err := reuse.getSourceIPs("udp4", raddr) + Expect(err).ToNot(HaveOccurred()) + Expect(ips).ToNot(BeEmpty()) + // listen + addr, err := net.ResolveUDPAddr("udp4", ips[0].String()+":0") + Expect(err).ToNot(HaveOccurred()) + lconn, err := reuse.Listen("udp4", addr) + Expect(err).ToNot(HaveOccurred()) + Expect(lconn.GetCount()).To(Equal(1)) + // dial + conn, err := reuse.Dial("udp4", raddr) + Expect(err).ToNot(HaveOccurred()) + Expect(conn.GetCount()).To(Equal(2)) + }) + } + }) + + Context("garbage-collecting connections", func() { + numGlobals := func() int { + reuse.mutex.Lock() + defer reuse.mutex.Unlock() + return len(reuse.global) + } + + BeforeEach(func() { + maxUnusedDuration = 100 * time.Millisecond + }) + + It("garbage collects connections once they're not used any more for a certain time", func() { + addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") + Expect(err).ToNot(HaveOccurred()) + lconn, err := reuse.Listen("udp4", addr) + Expect(err).ToNot(HaveOccurred()) + Expect(lconn.GetCount()).To(Equal(1)) + + closeTime := time.Now() + lconn.DecreaseCount() + + for { + num := numGlobals() + if closeTime.Add(maxUnusedDuration).Before(time.Now()) { + break + } + Expect(num).To(Equal(1)) + time.Sleep(2 * time.Millisecond) + } + Eventually(numGlobals).Should(BeZero()) + }) + + It("only stops the garbage collector when there are no more connections", func() { + addr1, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") + Expect(err).ToNot(HaveOccurred()) + conn1, err := reuse.Listen("udp4", addr1) + Expect(err).ToNot(HaveOccurred()) + + addr2, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") + Expect(err).ToNot(HaveOccurred()) + conn2, err := reuse.Listen("udp4", addr2) + Expect(err).ToNot(HaveOccurred()) + + Eventually(isGarbageCollectorRunning).Should(BeTrue()) + conn1.DecreaseCount() + Consistently(isGarbageCollectorRunning, 2*maxUnusedDuration).Should(BeTrue()) + conn2.DecreaseCount() + Eventually(isGarbageCollectorRunning, 2*maxUnusedDuration).Should(BeFalse()) + }) + }) }) From 2fe3c88eb8656c7e673c5ba6ffc01f5c515c5a29 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 11 Aug 2019 15:33:53 +0700 Subject: [PATCH 058/138] close reuse-connections before deleting them --- p2p/transport/quic/reuse.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go index 2903b68384..cb45aa2307 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse.go @@ -84,12 +84,14 @@ func (r *reuse) runGarbageCollector() { r.mutex.Lock() for key, conn := range r.global { if conn.ShouldGarbageCollect(now) { + conn.Close() delete(r.global, key) } } for ukey, conns := range r.unicast { for key, conn := range conns { if conn.ShouldGarbageCollect(now) { + conn.Close() delete(conns, key) } } From 70edc02c26b41f49261d3b8c37c4c3df8ddc7c02 Mon Sep 17 00:00:00 2001 From: Guilhem Fanton Date: Wed, 28 Aug 2019 17:29:10 +0200 Subject: [PATCH 059/138] fix(android): use specific netlink families for android Android doesn't allow netlink_xfrm & netlink_nflog in his base policy in enforce mode. (see [here](https://android.googlesource.com/platform/system/sepolicy/+/3aa1c1725ea2b6fd452c5771629dcfc50a351538/public/app.te#396)) this cause a _permission denied_ when NewTransport method is called on android, however it's look like that only *netlink route* is needed, so we just have to limit netlink families support on android build to NETLINK_ROUTE only when netlink.NewHandle is called. --- p2p/transport/quic/netlink_android.go | 8 ++++++++ p2p/transport/quic/netlink_other.go | 8 ++++++++ p2p/transport/quic/reuse.go | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 p2p/transport/quic/netlink_android.go create mode 100644 p2p/transport/quic/netlink_other.go diff --git a/p2p/transport/quic/netlink_android.go b/p2p/transport/quic/netlink_android.go new file mode 100644 index 0000000000..07625263c7 --- /dev/null +++ b/p2p/transport/quic/netlink_android.go @@ -0,0 +1,8 @@ +// +build android + +package libp2pquic + +import "golang.org/x/sys/unix" + +// Android doesn't allow netlink_xfrm and netlink_netfilter in his base policy +var SupportedNlFamilies = []int{unix.NETLINK_ROUTE} diff --git a/p2p/transport/quic/netlink_other.go b/p2p/transport/quic/netlink_other.go new file mode 100644 index 0000000000..47cc9d69bc --- /dev/null +++ b/p2p/transport/quic/netlink_other.go @@ -0,0 +1,8 @@ +// +build !android + +package libp2pquic + +import "github.com/vishvananda/netlink/nl" + +// nl.SupportedNlFamilies is the default netlink families used by the netlink package +var SupportedNlFamilies = nl.SupportedNlFamilies diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go index cb45aa2307..c6415b4d8f 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse.go @@ -62,7 +62,7 @@ type reuse struct { func newReuse() (*reuse, error) { // On non-Linux systems, this will return ErrNotImplemented. - handle, err := netlink.NewHandle() + handle, err := netlink.NewHandle(SupportedNlFamilies...) if err == netlink.ErrNotImplemented { handle = nil } else if err != nil { From 7820ddc8dd2062c5521dc4730ed3cf7d8190cd95 Mon Sep 17 00:00:00 2001 From: Guilhem Fanton Date: Fri, 30 Aug 2019 15:43:22 +0200 Subject: [PATCH 060/138] fix(netlink): limit netlink families to NETLINK_ROUTE on linux instead of android only. --- p2p/transport/quic/netlink_android.go | 8 -------- p2p/transport/quic/netlink_linux.go | 10 ++++++++++ p2p/transport/quic/netlink_other.go | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) delete mode 100644 p2p/transport/quic/netlink_android.go create mode 100644 p2p/transport/quic/netlink_linux.go diff --git a/p2p/transport/quic/netlink_android.go b/p2p/transport/quic/netlink_android.go deleted file mode 100644 index 07625263c7..0000000000 --- a/p2p/transport/quic/netlink_android.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build android - -package libp2pquic - -import "golang.org/x/sys/unix" - -// Android doesn't allow netlink_xfrm and netlink_netfilter in his base policy -var SupportedNlFamilies = []int{unix.NETLINK_ROUTE} diff --git a/p2p/transport/quic/netlink_linux.go b/p2p/transport/quic/netlink_linux.go new file mode 100644 index 0000000000..a5f6ffe6e1 --- /dev/null +++ b/p2p/transport/quic/netlink_linux.go @@ -0,0 +1,10 @@ +// +build linux + +package libp2pquic + +import "golang.org/x/sys/unix" + +// We just need netlink_route here. +// note: We should avoid the use of netlink_xfrm or netlink_netfilter has it is +// not allowed by Android in his base policy. +var SupportedNlFamilies = []int{unix.NETLINK_ROUTE} diff --git a/p2p/transport/quic/netlink_other.go b/p2p/transport/quic/netlink_other.go index 47cc9d69bc..58ad3ca124 100644 --- a/p2p/transport/quic/netlink_other.go +++ b/p2p/transport/quic/netlink_other.go @@ -1,4 +1,4 @@ -// +build !android +// +build !linux package libp2pquic From 9fd36745d027dfcda138bfff1d58b048e519c449 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 15 Nov 2019 14:39:26 +0800 Subject: [PATCH 061/138] make reuse work on Windows --- p2p/transport/quic/libp2pquic_suite_test.go | 2 +- p2p/transport/quic/netlink_other.go | 3 +- .../quic/{reuse.go => reuse_base.go} | 68 +++---------------- p2p/transport/quic/reuse_linux_test.go | 42 ++++++++++++ p2p/transport/quic/reuse_not_win.go | 66 ++++++++++++++++++ p2p/transport/quic/reuse_test.go | 57 ++++++---------- p2p/transport/quic/reuse_win.go | 26 +++++++ 7 files changed, 165 insertions(+), 99 deletions(-) rename p2p/transport/quic/{reuse.go => reuse_base.go} (70%) create mode 100644 p2p/transport/quic/reuse_linux_test.go create mode 100644 p2p/transport/quic/reuse_not_win.go create mode 100644 p2p/transport/quic/reuse_win.go diff --git a/p2p/transport/quic/libp2pquic_suite_test.go b/p2p/transport/quic/libp2pquic_suite_test.go index ce48c3f619..a2e1df400d 100644 --- a/p2p/transport/quic/libp2pquic_suite_test.go +++ b/p2p/transport/quic/libp2pquic_suite_test.go @@ -28,7 +28,7 @@ var maxUnusedDurationOrig time.Duration func isGarbageCollectorRunning() bool { var b bytes.Buffer pprof.Lookup("goroutine").WriteTo(&b, 1) - return strings.Contains(b.String(), "go-libp2p-quic-transport.(*reuse).runGarbageCollector") + return strings.Contains(b.String(), "go-libp2p-quic-transport.(*reuseBase).runGarbageCollector") } var _ = BeforeEach(func() { diff --git a/p2p/transport/quic/netlink_other.go b/p2p/transport/quic/netlink_other.go index 58ad3ca124..ccc073da0b 100644 --- a/p2p/transport/quic/netlink_other.go +++ b/p2p/transport/quic/netlink_other.go @@ -1,8 +1,9 @@ // +build !linux +// +build !windows package libp2pquic import "github.com/vishvananda/netlink/nl" -// nl.SupportedNlFamilies is the default netlink families used by the netlink package +// SupportedNlFamilies is the default netlink families used by the netlink package var SupportedNlFamilies = nl.SupportedNlFamilies diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse_base.go similarity index 70% rename from p2p/transport/quic/reuse.go rename to p2p/transport/quic/reuse_base.go index c6415b4d8f..347a9a6cf7 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse_base.go @@ -4,11 +4,9 @@ import ( "net" "sync" "time" - - "github.com/vishvananda/netlink" ) -// Constants. Defined as variables to simplify testing. +// Constant. Defined as variables to simplify testing. var ( garbageCollectInterval = 30 * time.Second maxUnusedDuration = 10 * time.Second @@ -48,34 +46,24 @@ func (c *reuseConn) ShouldGarbageCollect(now time.Time) bool { return !c.unusedSince.IsZero() && c.unusedSince.Add(maxUnusedDuration).Before(now) } -type reuse struct { +type reuseBase struct { mutex sync.Mutex garbageCollectorRunning bool - handle *netlink.Handle // Only set on Linux. nil on other systems. - unicast map[string] /* IP.String() */ map[int] /* port */ *reuseConn // global contains connections that are listening on 0.0.0.0 / :: global map[int]*reuseConn } -func newReuse() (*reuse, error) { - // On non-Linux systems, this will return ErrNotImplemented. - handle, err := netlink.NewHandle(SupportedNlFamilies...) - if err == netlink.ErrNotImplemented { - handle = nil - } else if err != nil { - return nil, err - } - return &reuse{ +func newReuseBase() reuseBase { + return reuseBase{ unicast: make(map[string]map[int]*reuseConn), global: make(map[int]*reuseConn), - handle: handle, - }, nil + } } -func (r *reuse) runGarbageCollector() { +func (r *reuseBase) runGarbageCollector() { ticker := time.NewTicker(garbageCollectInterval) defer ticker.Stop() @@ -114,52 +102,14 @@ func (r *reuse) runGarbageCollector() { } // must be called while holding the mutex -func (r *reuse) maybeStartGarbageCollector() { +func (r *reuseBase) maybeStartGarbageCollector() { if !r.garbageCollectorRunning { r.garbageCollectorRunning = true go r.runGarbageCollector() } } -// Get the source IP that the kernel would use for dialing. -// This only works on Linux. -// On other systems, this returns an empty slice of IP addresses. -func (r *reuse) getSourceIPs(network string, raddr *net.UDPAddr) ([]net.IP, error) { - if r.handle == nil { - return nil, nil - } - - routes, err := r.handle.RouteGet(raddr.IP) - if err != nil { - return nil, err - } - - ips := make([]net.IP, 0, len(routes)) - for _, route := range routes { - ips = append(ips, route.Src) - } - return ips, nil -} - -func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { - ips, err := r.getSourceIPs(network, raddr) - if err != nil { - return nil, err - } - - r.mutex.Lock() - defer r.mutex.Unlock() - - conn, err := r.dialLocked(network, raddr, ips) - if err != nil { - return nil, err - } - conn.IncreaseCount() - r.maybeStartGarbageCollector() - return conn, nil -} - -func (r *reuse) dialLocked(network string, raddr *net.UDPAddr, ips []net.IP) (*reuseConn, error) { +func (r *reuseBase) dialLocked(network string, raddr *net.UDPAddr, ips []net.IP) (*reuseConn, error) { for _, ip := range ips { // We already have at least one suitable connection... if conns, ok := r.unicast[ip.String()]; ok { @@ -194,7 +144,7 @@ func (r *reuse) dialLocked(network string, raddr *net.UDPAddr, ips []net.IP) (*r return rconn, nil } -func (r *reuse) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { +func (r *reuseBase) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { conn, err := net.ListenUDP(network, laddr) if err != nil { return nil, err diff --git a/p2p/transport/quic/reuse_linux_test.go b/p2p/transport/quic/reuse_linux_test.go new file mode 100644 index 0000000000..8bc401a0c8 --- /dev/null +++ b/p2p/transport/quic/reuse_linux_test.go @@ -0,0 +1,42 @@ +// +build linux + +package libp2pquic + +import ( + "net" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Reuse (on Linux)", func() { + var reuse *reuse + + BeforeEach(func() { + var err error + reuse, err = newReuse() + Expect(err).ToNot(HaveOccurred()) + }) + + Context("creating and reusing connections", func() { + AfterEach(func() { closeAllConns(reuse) }) + + It("reuses a connection it created for listening on a specific interface", func() { + raddr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") + Expect(err).ToNot(HaveOccurred()) + ips, err := reuse.getSourceIPs("udp4", raddr) + Expect(err).ToNot(HaveOccurred()) + Expect(ips).ToNot(BeEmpty()) + // listen + addr, err := net.ResolveUDPAddr("udp4", ips[0].String()+":0") + Expect(err).ToNot(HaveOccurred()) + lconn, err := reuse.Listen("udp4", addr) + Expect(err).ToNot(HaveOccurred()) + Expect(lconn.GetCount()).To(Equal(1)) + // dial + conn, err := reuse.Dial("udp4", raddr) + Expect(err).ToNot(HaveOccurred()) + Expect(conn.GetCount()).To(Equal(2)) + }) + }) +}) diff --git a/p2p/transport/quic/reuse_not_win.go b/p2p/transport/quic/reuse_not_win.go new file mode 100644 index 0000000000..fb36b83e89 --- /dev/null +++ b/p2p/transport/quic/reuse_not_win.go @@ -0,0 +1,66 @@ +// +build !windows + +package libp2pquic + +import ( + "net" + + "github.com/vishvananda/netlink" +) + +type reuse struct { + reuseBase + + handle *netlink.Handle // Only set on Linux. nil on other systems. +} + +func newReuse() (*reuse, error) { + handle, err := netlink.NewHandle(SupportedNlFamilies...) + if err == netlink.ErrNotImplemented { + handle = nil + } else if err != nil { + return nil, err + } + return &reuse{ + reuseBase: newReuseBase(), + handle: handle, + }, nil +} + +// Get the source IP that the kernel would use for dialing. +// This only works on Linux. +// On other systems, this returns an empty slice of IP addresses. +func (r *reuse) getSourceIPs(network string, raddr *net.UDPAddr) ([]net.IP, error) { + if r.handle == nil { + return nil, nil + } + + routes, err := r.handle.RouteGet(raddr.IP) + if err != nil { + return nil, err + } + + ips := make([]net.IP, 0, len(routes)) + for _, route := range routes { + ips = append(ips, route.Src) + } + return ips, nil +} + +func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { + ips, err := r.getSourceIPs(network, raddr) + if err != nil { + return nil, err + } + + r.mutex.Lock() + defer r.mutex.Unlock() + + conn, err := r.dialLocked(network, raddr, ips) + if err != nil { + return nil, err + } + conn.IncreaseCount() + r.maybeStartGarbageCollector() + return conn, nil +} diff --git a/p2p/transport/quic/reuse_test.go b/p2p/transport/quic/reuse_test.go index 276fd5bd95..e7739156e7 100644 --- a/p2p/transport/quic/reuse_test.go +++ b/p2p/transport/quic/reuse_test.go @@ -2,7 +2,6 @@ package libp2pquic import ( "net" - "runtime" "time" . "github.com/onsi/ginkgo" @@ -15,6 +14,24 @@ func (c *reuseConn) GetCount() int { return c.refCount } +func closeAllConns(reuse *reuse) { + reuse.mutex.Lock() + for _, conn := range reuse.global { + for conn.GetCount() > 0 { + conn.DecreaseCount() + } + } + for _, conns := range reuse.unicast { + for _, conn := range conns { + for conn.GetCount() > 0 { + conn.DecreaseCount() + } + } + } + reuse.mutex.Unlock() + Eventually(isGarbageCollectorRunning).Should(BeFalse()) +} + var _ = Describe("Reuse", func() { var reuse *reuse @@ -25,23 +42,7 @@ var _ = Describe("Reuse", func() { }) Context("creating and reusing connections", func() { - AfterEach(func() { - reuse.mutex.Lock() - for _, conn := range reuse.global { - for conn.GetCount() > 0 { - conn.DecreaseCount() - } - } - for _, conns := range reuse.unicast { - for _, conn := range conns { - for conn.GetCount() > 0 { - conn.DecreaseCount() - } - } - } - reuse.mutex.Unlock() - Eventually(isGarbageCollectorRunning).Should(BeFalse()) - }) + AfterEach(func() { closeAllConns(reuse) }) It("creates a new global connection when listening on 0.0.0.0", func() { addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") @@ -84,26 +85,6 @@ var _ = Describe("Reuse", func() { Expect(err).ToNot(HaveOccurred()) Expect(conn.GetCount()).To(Equal(2)) }) - - if runtime.GOOS == "linux" { - It("reuses a connection it created for listening on a specific interface", func() { - raddr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") - Expect(err).ToNot(HaveOccurred()) - ips, err := reuse.getSourceIPs("udp4", raddr) - Expect(err).ToNot(HaveOccurred()) - Expect(ips).ToNot(BeEmpty()) - // listen - addr, err := net.ResolveUDPAddr("udp4", ips[0].String()+":0") - Expect(err).ToNot(HaveOccurred()) - lconn, err := reuse.Listen("udp4", addr) - Expect(err).ToNot(HaveOccurred()) - Expect(lconn.GetCount()).To(Equal(1)) - // dial - conn, err := reuse.Dial("udp4", raddr) - Expect(err).ToNot(HaveOccurred()) - Expect(conn.GetCount()).To(Equal(2)) - }) - } }) Context("garbage-collecting connections", func() { diff --git a/p2p/transport/quic/reuse_win.go b/p2p/transport/quic/reuse_win.go new file mode 100644 index 0000000000..0f57c8e0ea --- /dev/null +++ b/p2p/transport/quic/reuse_win.go @@ -0,0 +1,26 @@ +// +build windows + +package libp2pquic + +import "net" + +type reuse struct { + reuseBase +} + +func newReuse() (*reuse, error) { + return &reuse{reuseBase: newReuseBase()}, nil +} + +func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { + r.mutex.Lock() + defer r.mutex.Unlock() + + conn, err := r.dialLocked(network, raddr, nil) + if err != nil { + return nil, err + } + conn.IncreaseCount() + r.maybeStartGarbageCollector() + return conn, nil +} From 9a31a773db42cbaefb0716accaf8b1b207825d70 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 15 Nov 2019 05:18:40 -0800 Subject: [PATCH 062/138] chore(dep): update deps --- p2p/transport/quic/conn_test.go | 2 +- p2p/transport/quic/listener_test.go | 2 +- p2p/transport/quic/transport.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index bf8d681149..882f2f0138 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -33,7 +33,7 @@ var _ = Describe("Connection", func() { priv, _, err = ic.GenerateECDSAKeyPair(rand.Reader) case 1: fmt.Fprintf(GinkgoWriter, " using an RSA key: ") - priv, _, err = ic.GenerateRSAKeyPair(1024, rand.Reader) + priv, _, err = ic.GenerateRSAKeyPair(2048, rand.Reader) case 2: fmt.Fprintf(GinkgoWriter, " using an Ed25519 key: ") priv, _, err = ic.GenerateEd25519Key(rand.Reader) diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index e90debc810..85c74fa7d7 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -19,7 +19,7 @@ var _ = Describe("Listener", func() { var t tpt.Transport BeforeEach(func() { - rsaKey, err := rsa.GenerateKey(rand.Reader, 1024) + rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) Expect(err).ToNot(HaveOccurred()) key, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(rsaKey)) Expect(err).ToNot(HaveOccurred()) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index a9ddd41355..03b55ca2bc 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -12,8 +12,8 @@ import ( quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" + "github.com/multiformats/go-multiaddr-fmt" manet "github.com/multiformats/go-multiaddr-net" - "github.com/whyrusleeping/mafmt" ) var quicConfig = &quic.Config{ From 81c4f0ad249b25fd692386ae4440cfad5a737bc7 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 17 Feb 2020 10:34:32 +0700 Subject: [PATCH 063/138] increase the stream and connection receive windows --- p2p/transport/quic/transport.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 03b55ca2bc..3445a638f4 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -12,15 +12,15 @@ import ( quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" - "github.com/multiformats/go-multiaddr-fmt" + mafmt "github.com/multiformats/go-multiaddr-fmt" manet "github.com/multiformats/go-multiaddr-net" ) var quicConfig = &quic.Config{ MaxIncomingStreams: 1000, - MaxIncomingUniStreams: -1, // disable unidirectional streams - MaxReceiveStreamFlowControlWindow: 3 * (1 << 20), // 3 MB - MaxReceiveConnectionFlowControlWindow: 4.5 * (1 << 20), // 4.5 MB + MaxIncomingUniStreams: -1, // disable unidirectional streams + MaxReceiveStreamFlowControlWindow: 10 * (1 << 20), // 10 MB + MaxReceiveConnectionFlowControlWindow: 15 * (1 << 20), // 15 MB AcceptToken: func(clientAddr net.Addr, _ *quic.Token) bool { // TODO(#6): require source address validation when under load return true From 9cdba6a4cb910f21368cd1051c32740b4a18c97b Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 19 Feb 2020 18:25:26 +0700 Subject: [PATCH 064/138] fix key comparisons in tests --- p2p/transport/quic/conn_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 882f2f0138..40a6bca89a 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -78,11 +78,11 @@ var _ = Describe("Connection", func() { Expect(conn.LocalPeer()).To(Equal(clientID)) Expect(conn.LocalPrivateKey()).To(Equal(clientKey)) Expect(conn.RemotePeer()).To(Equal(serverID)) - Expect(conn.RemotePublicKey()).To(Equal(serverKey.GetPublic())) + Expect(conn.RemotePublicKey().Equals(serverKey.GetPublic())).To(BeTrue()) Expect(serverConn.LocalPeer()).To(Equal(serverID)) Expect(serverConn.LocalPrivateKey()).To(Equal(serverKey)) Expect(serverConn.RemotePeer()).To(Equal(clientID)) - Expect(serverConn.RemotePublicKey()).To(Equal(clientKey.GetPublic())) + Expect(serverConn.RemotePublicKey().Equals(clientKey.GetPublic())).To(BeTrue()) }) It("handshakes on IPv6", func() { @@ -102,11 +102,11 @@ var _ = Describe("Connection", func() { Expect(conn.LocalPeer()).To(Equal(clientID)) Expect(conn.LocalPrivateKey()).To(Equal(clientKey)) Expect(conn.RemotePeer()).To(Equal(serverID)) - Expect(conn.RemotePublicKey()).To(Equal(serverKey.GetPublic())) + Expect(conn.RemotePublicKey().Equals(serverKey.GetPublic())).To(BeTrue()) Expect(serverConn.LocalPeer()).To(Equal(serverID)) Expect(serverConn.LocalPrivateKey()).To(Equal(serverKey)) Expect(serverConn.RemotePeer()).To(Equal(clientID)) - Expect(serverConn.RemotePublicKey()).To(Equal(clientKey.GetPublic())) + Expect(serverConn.RemotePublicKey().Equals(clientKey.GetPublic())).To(BeTrue()) }) It("opens and accepts streams", func() { From f40e281c775c70cf5eb720ded7ef1c8506be287e Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 1 Mar 2020 13:01:29 +0700 Subject: [PATCH 065/138] update quic-go to v0.15.0 --- p2p/transport/quic/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index 75e2193692..9ba8593b72 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -28,7 +28,7 @@ type conn struct { var _ tpt.CapableConn = &conn{} func (c *conn) Close() error { - return c.sess.Close() + return c.sess.CloseWithError(0, "") } // IsClosed returns whether a connection is fully closed. From b8f591a4bec903217c96dad57e0389bef5ed6fbb Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Sat, 7 Mar 2020 03:13:03 +0200 Subject: [PATCH 066/138] Respect mux.ErrReset (#113) --- p2p/transport/quic/stream.go | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/p2p/transport/quic/stream.go b/p2p/transport/quic/stream.go index e757de20dd..313ea227ec 100644 --- a/p2p/transport/quic/stream.go +++ b/p2p/transport/quic/stream.go @@ -6,14 +6,36 @@ import ( quic "github.com/lucas-clemente/quic-go" ) +const ( + reset quic.ErrorCode = 0 +) + type stream struct { quic.Stream } -var _ mux.MuxedStream = &stream{} +func (s *stream) Read(b []byte) (n int, err error) { + n, err = s.Stream.Read(b) + if serr, ok := err.(quic.StreamError); ok && serr.Canceled() { + err = mux.ErrReset + } + + return n, err +} + +func (s *stream) Write(b []byte) (n int, err error) { + n, err = s.Stream.Write(b) + if serr, ok := err.(quic.StreamError); ok && serr.Canceled() { + err = mux.ErrReset + } + + return n, err +} func (s *stream) Reset() error { - s.Stream.CancelRead(0) - s.Stream.CancelWrite(0) + s.Stream.CancelRead(reset) + s.Stream.CancelWrite(reset) return nil } + +var _ mux.MuxedStream = &stream{} From eea1dd74b53de8e9031f9a33c26907ac7a9e7996 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 20 Feb 2020 12:45:35 +0700 Subject: [PATCH 067/138] accept a PSK in the transport constructor (and reject it) --- p2p/transport/quic/conn_test.go | 22 +++++++++++----------- p2p/transport/quic/listener_test.go | 2 +- p2p/transport/quic/transport.go | 11 +++++++++-- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 40a6bca89a..ef234bc5bc 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -62,12 +62,12 @@ var _ = Describe("Connection", func() { }) It("handshakes on IPv4", func() { - serverTransport, err := NewTransport(serverKey) + serverTransport, err := NewTransport(serverKey, nil) Expect(err).ToNot(HaveOccurred()) ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() - clientTransport, err := NewTransport(clientKey) + clientTransport, err := NewTransport(clientKey, nil) Expect(err).ToNot(HaveOccurred()) conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) @@ -86,12 +86,12 @@ var _ = Describe("Connection", func() { }) It("handshakes on IPv6", func() { - serverTransport, err := NewTransport(serverKey) + serverTransport, err := NewTransport(serverKey, nil) Expect(err).ToNot(HaveOccurred()) ln := runServer(serverTransport, "/ip6/::1/udp/0/quic") defer ln.Close() - clientTransport, err := NewTransport(clientKey) + clientTransport, err := NewTransport(clientKey, nil) Expect(err).ToNot(HaveOccurred()) conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) @@ -110,12 +110,12 @@ var _ = Describe("Connection", func() { }) It("opens and accepts streams", func() { - serverTransport, err := NewTransport(serverKey) + serverTransport, err := NewTransport(serverKey, nil) Expect(err).ToNot(HaveOccurred()) ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() - clientTransport, err := NewTransport(clientKey) + clientTransport, err := NewTransport(clientKey, nil) Expect(err).ToNot(HaveOccurred()) conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) @@ -139,11 +139,11 @@ var _ = Describe("Connection", func() { It("fails if the peer ID doesn't match", func() { thirdPartyID, _ := createPeer() - serverTransport, err := NewTransport(serverKey) + serverTransport, err := NewTransport(serverKey, nil) Expect(err).ToNot(HaveOccurred()) ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") - clientTransport, err := NewTransport(clientKey) + clientTransport, err := NewTransport(clientKey, nil) Expect(err).ToNot(HaveOccurred()) // dial, but expect the wrong peer ID _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), thirdPartyID) @@ -164,10 +164,10 @@ var _ = Describe("Connection", func() { It("dials to two servers at the same time", func() { serverID2, serverKey2 := createPeer() - serverTransport, err := NewTransport(serverKey) + serverTransport, err := NewTransport(serverKey, nil) Expect(err).ToNot(HaveOccurred()) ln1 := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") - serverTransport2, err := NewTransport(serverKey2) + serverTransport2, err := NewTransport(serverKey2, nil) defer ln1.Close() Expect(err).ToNot(HaveOccurred()) ln2 := runServer(serverTransport2, "/ip4/127.0.0.1/udp/0/quic") @@ -194,7 +194,7 @@ var _ = Describe("Connection", func() { } }() - clientTransport, err := NewTransport(clientKey) + clientTransport, err := NewTransport(clientKey, nil) Expect(err).ToNot(HaveOccurred()) c1, err := clientTransport.Dial(context.Background(), ln1.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index 85c74fa7d7..ac7716f353 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -23,7 +23,7 @@ var _ = Describe("Listener", func() { Expect(err).ToNot(HaveOccurred()) key, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(rsaKey)) Expect(err).ToNot(HaveOccurred()) - t, err = NewTransport(key) + t, err = NewTransport(key, nil) Expect(err).ToNot(HaveOccurred()) }) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 3445a638f4..6a61eae61e 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -5,17 +5,20 @@ import ( "errors" "net" + logging "github.com/ipfs/go-log" ic "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/pnet" tpt "github.com/libp2p/go-libp2p-core/transport" p2ptls "github.com/libp2p/go-libp2p-tls" - quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" mafmt "github.com/multiformats/go-multiaddr-fmt" manet "github.com/multiformats/go-multiaddr-net" ) +var log = logging.Logger("quic-transport") + var quicConfig = &quic.Config{ MaxIncomingStreams: 1000, MaxIncomingUniStreams: -1, // disable unidirectional streams @@ -86,7 +89,11 @@ type transport struct { var _ tpt.Transport = &transport{} // NewTransport creates a new QUIC transport -func NewTransport(key ic.PrivKey) (tpt.Transport, error) { +func NewTransport(key ic.PrivKey, psk pnet.PSK) (tpt.Transport, error) { + if len(psk) > 0 { + log.Error("QUIC doesn't support private networks yet.") + return nil, errors.New("QUIC doesn't support private networks yet") + } localPeer, err := peer.IDFromPrivateKey(key) if err != nil { return nil, err From 29c56d8b005c84145761b5ad2b96ecc6bce3cb19 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 15 Mar 2020 09:33:40 +0700 Subject: [PATCH 068/138] add support for multiaddr filtering --- p2p/transport/quic/conn_test.go | 57 +++++++++++++++++---- p2p/transport/quic/filtered_conn.go | 34 ++++++++++++ p2p/transport/quic/libp2pquic_suite_test.go | 15 ++++-- p2p/transport/quic/listener_test.go | 2 +- p2p/transport/quic/reuse_base.go | 16 ++++-- p2p/transport/quic/reuse_linux_test.go | 2 +- p2p/transport/quic/reuse_not_win.go | 6 ++- p2p/transport/quic/reuse_test.go | 2 +- p2p/transport/quic/reuse_win.go | 10 ++-- p2p/transport/quic/transport.go | 11 ++-- 10 files changed, 123 insertions(+), 32 deletions(-) create mode 100644 p2p/transport/quic/filtered_conn.go diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index ef234bc5bc..f7d7253c9c 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -7,11 +7,13 @@ import ( "fmt" "io/ioutil" mrand "math/rand" + "net" "time" ic "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/peer" tpt "github.com/libp2p/go-libp2p-core/transport" + filter "github.com/libp2p/go-maddr-filter" ma "github.com/multiformats/go-multiaddr" . "github.com/onsi/ginkgo" @@ -62,12 +64,12 @@ var _ = Describe("Connection", func() { }) It("handshakes on IPv4", func() { - serverTransport, err := NewTransport(serverKey, nil) + serverTransport, err := NewTransport(serverKey, nil, nil) Expect(err).ToNot(HaveOccurred()) ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() - clientTransport, err := NewTransport(clientKey, nil) + clientTransport, err := NewTransport(clientKey, nil, nil) Expect(err).ToNot(HaveOccurred()) conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) @@ -86,12 +88,12 @@ var _ = Describe("Connection", func() { }) It("handshakes on IPv6", func() { - serverTransport, err := NewTransport(serverKey, nil) + serverTransport, err := NewTransport(serverKey, nil, nil) Expect(err).ToNot(HaveOccurred()) ln := runServer(serverTransport, "/ip6/::1/udp/0/quic") defer ln.Close() - clientTransport, err := NewTransport(clientKey, nil) + clientTransport, err := NewTransport(clientKey, nil, nil) Expect(err).ToNot(HaveOccurred()) conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) @@ -110,12 +112,12 @@ var _ = Describe("Connection", func() { }) It("opens and accepts streams", func() { - serverTransport, err := NewTransport(serverKey, nil) + serverTransport, err := NewTransport(serverKey, nil, nil) Expect(err).ToNot(HaveOccurred()) ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() - clientTransport, err := NewTransport(clientKey, nil) + clientTransport, err := NewTransport(clientKey, nil, nil) Expect(err).ToNot(HaveOccurred()) conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) @@ -139,11 +141,11 @@ var _ = Describe("Connection", func() { It("fails if the peer ID doesn't match", func() { thirdPartyID, _ := createPeer() - serverTransport, err := NewTransport(serverKey, nil) + serverTransport, err := NewTransport(serverKey, nil, nil) Expect(err).ToNot(HaveOccurred()) ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") - clientTransport, err := NewTransport(clientKey, nil) + clientTransport, err := NewTransport(clientKey, nil, nil) Expect(err).ToNot(HaveOccurred()) // dial, but expect the wrong peer ID _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), thirdPartyID) @@ -161,14 +163,47 @@ var _ = Describe("Connection", func() { Eventually(done).Should(BeClosed()) }) + It("filters addresses", func() { + filters := filter.NewFilters() + ipNet := net.IPNet{ + IP: net.IPv4(127, 0, 0, 1), + Mask: net.IPv4Mask(255, 255, 255, 255), + } + filters.AddFilter(ipNet, filter.ActionDeny) + testMA, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234/quic") + Expect(err).ToNot(HaveOccurred()) + Expect(filters.AddrBlocked(testMA)).To(BeTrue()) + + serverTransport, err := NewTransport(serverKey, nil, filters) + Expect(err).ToNot(HaveOccurred()) + ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") + defer ln.Close() + + clientTransport, err := NewTransport(clientKey, nil, nil) + Expect(err).ToNot(HaveOccurred()) + + // make sure that connection attempts fails + quicConfig.HandshakeTimeout = 250 * time.Millisecond + _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) + Expect(err).To(HaveOccurred()) + Expect(err.(net.Error).Timeout()).To(BeTrue()) + + // now allow the address and make sure the connection goes through + quicConfig.HandshakeTimeout = 2 * time.Second + filters.AddFilter(ipNet, filter.ActionAccept) + conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) + Expect(err).ToNot(HaveOccurred()) + conn.Close() + }) + It("dials to two servers at the same time", func() { serverID2, serverKey2 := createPeer() - serverTransport, err := NewTransport(serverKey, nil) + serverTransport, err := NewTransport(serverKey, nil, nil) Expect(err).ToNot(HaveOccurred()) ln1 := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") - serverTransport2, err := NewTransport(serverKey2, nil) defer ln1.Close() + serverTransport2, err := NewTransport(serverKey2, nil, nil) Expect(err).ToNot(HaveOccurred()) ln2 := runServer(serverTransport2, "/ip4/127.0.0.1/udp/0/quic") defer ln2.Close() @@ -194,7 +229,7 @@ var _ = Describe("Connection", func() { } }() - clientTransport, err := NewTransport(clientKey, nil) + clientTransport, err := NewTransport(clientKey, nil, nil) Expect(err).ToNot(HaveOccurred()) c1, err := clientTransport.Dial(context.Background(), ln1.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) diff --git a/p2p/transport/quic/filtered_conn.go b/p2p/transport/quic/filtered_conn.go new file mode 100644 index 0000000000..dc60bb08e2 --- /dev/null +++ b/p2p/transport/quic/filtered_conn.go @@ -0,0 +1,34 @@ +package libp2pquic + +import ( + "net" + + filter "github.com/libp2p/go-maddr-filter" +) + +type filteredConn struct { + net.PacketConn + + filters *filter.Filters +} + +func newFilteredConn(c net.PacketConn, filters *filter.Filters) net.PacketConn { + return &filteredConn{PacketConn: c, filters: filters} +} + +func (c *filteredConn) ReadFrom(b []byte) (n int, addr net.Addr, rerr error) { + for { + n, addr, rerr = c.PacketConn.ReadFrom(b) + // Short Header packet, see https://tools.ietf.org/html/draft-ietf-quic-invariants-07#section-4.2. + if n < 1 || b[0]&0x80 == 0 { + return + } + maddr, err := toQuicMultiaddr(addr) + if err != nil { + panic(err) + } + if !c.filters.AddrBlocked(maddr) { + return + } + } +} diff --git a/p2p/transport/quic/libp2pquic_suite_test.go b/p2p/transport/quic/libp2pquic_suite_test.go index a2e1df400d..d6ae1510fd 100644 --- a/p2p/transport/quic/libp2pquic_suite_test.go +++ b/p2p/transport/quic/libp2pquic_suite_test.go @@ -5,12 +5,13 @@ import ( mrand "math/rand" "runtime/pprof" "strings" + "testing" "time" + "github.com/lucas-clemente/quic-go" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - - "testing" ) func TestLibp2pQuicTransport(t *testing.T) { @@ -22,8 +23,11 @@ var _ = BeforeSuite(func() { mrand.Seed(GinkgoRandomSeed()) }) -var garbageCollectIntervalOrig time.Duration -var maxUnusedDurationOrig time.Duration +var ( + garbageCollectIntervalOrig time.Duration + maxUnusedDurationOrig time.Duration + origQuicConfig *quic.Config +) func isGarbageCollectorRunning() bool { var b bytes.Buffer @@ -37,10 +41,13 @@ var _ = BeforeEach(func() { maxUnusedDurationOrig = maxUnusedDuration garbageCollectInterval = 50 * time.Millisecond maxUnusedDuration = 0 + origQuicConfig = quicConfig + quicConfig = quicConfig.Clone() }) var _ = AfterEach(func() { Eventually(isGarbageCollectorRunning).Should(BeFalse()) garbageCollectInterval = garbageCollectIntervalOrig maxUnusedDuration = maxUnusedDurationOrig + quicConfig = origQuicConfig }) diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index ac7716f353..3388f1ac37 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -23,7 +23,7 @@ var _ = Describe("Listener", func() { Expect(err).ToNot(HaveOccurred()) key, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(rsaKey)) Expect(err).ToNot(HaveOccurred()) - t, err = NewTransport(key, nil) + t, err = NewTransport(key, nil, nil) Expect(err).ToNot(HaveOccurred()) }) diff --git a/p2p/transport/quic/reuse_base.go b/p2p/transport/quic/reuse_base.go index 347a9a6cf7..053b777395 100644 --- a/p2p/transport/quic/reuse_base.go +++ b/p2p/transport/quic/reuse_base.go @@ -4,6 +4,8 @@ import ( "net" "sync" "time" + + filter "github.com/libp2p/go-maddr-filter" ) // Constant. Defined as variables to simplify testing. @@ -20,7 +22,10 @@ type reuseConn struct { unusedSince time.Time } -func newReuseConn(conn net.PacketConn) *reuseConn { +func newReuseConn(conn net.PacketConn, filters *filter.Filters) *reuseConn { + if filters != nil { + conn = newFilteredConn(conn, filters) + } return &reuseConn{PacketConn: conn} } @@ -49,6 +54,8 @@ func (c *reuseConn) ShouldGarbageCollect(now time.Time) bool { type reuseBase struct { mutex sync.Mutex + filters *filter.Filters + garbageCollectorRunning bool unicast map[string] /* IP.String() */ map[int] /* port */ *reuseConn @@ -56,8 +63,9 @@ type reuseBase struct { global map[int]*reuseConn } -func newReuseBase() reuseBase { +func newReuseBase(filters *filter.Filters) reuseBase { return reuseBase{ + filters: filters, unicast: make(map[string]map[int]*reuseConn), global: make(map[int]*reuseConn), } @@ -139,7 +147,7 @@ func (r *reuseBase) dialLocked(network string, raddr *net.UDPAddr, ips []net.IP) if err != nil { return nil, err } - rconn := newReuseConn(conn) + rconn := newReuseConn(conn, r.filters) r.global[conn.LocalAddr().(*net.UDPAddr).Port] = rconn return rconn, nil } @@ -151,7 +159,7 @@ func (r *reuseBase) Listen(network string, laddr *net.UDPAddr) (*reuseConn, erro } localAddr := conn.LocalAddr().(*net.UDPAddr) - rconn := newReuseConn(conn) + rconn := newReuseConn(conn, r.filters) rconn.IncreaseCount() r.mutex.Lock() diff --git a/p2p/transport/quic/reuse_linux_test.go b/p2p/transport/quic/reuse_linux_test.go index 8bc401a0c8..6fe77dbbf6 100644 --- a/p2p/transport/quic/reuse_linux_test.go +++ b/p2p/transport/quic/reuse_linux_test.go @@ -14,7 +14,7 @@ var _ = Describe("Reuse (on Linux)", func() { BeforeEach(func() { var err error - reuse, err = newReuse() + reuse, err = newReuse(nil) Expect(err).ToNot(HaveOccurred()) }) diff --git a/p2p/transport/quic/reuse_not_win.go b/p2p/transport/quic/reuse_not_win.go index fb36b83e89..57097a3098 100644 --- a/p2p/transport/quic/reuse_not_win.go +++ b/p2p/transport/quic/reuse_not_win.go @@ -5,6 +5,8 @@ package libp2pquic import ( "net" + filter "github.com/libp2p/go-maddr-filter" + "github.com/vishvananda/netlink" ) @@ -14,7 +16,7 @@ type reuse struct { handle *netlink.Handle // Only set on Linux. nil on other systems. } -func newReuse() (*reuse, error) { +func newReuse(filters *filter.Filters) (*reuse, error) { handle, err := netlink.NewHandle(SupportedNlFamilies...) if err == netlink.ErrNotImplemented { handle = nil @@ -22,7 +24,7 @@ func newReuse() (*reuse, error) { return nil, err } return &reuse{ - reuseBase: newReuseBase(), + reuseBase: newReuseBase(filters), handle: handle, }, nil } diff --git a/p2p/transport/quic/reuse_test.go b/p2p/transport/quic/reuse_test.go index e7739156e7..6a24d5d575 100644 --- a/p2p/transport/quic/reuse_test.go +++ b/p2p/transport/quic/reuse_test.go @@ -37,7 +37,7 @@ var _ = Describe("Reuse", func() { BeforeEach(func() { var err error - reuse, err = newReuse() + reuse, err = newReuse(nil) Expect(err).ToNot(HaveOccurred()) }) diff --git a/p2p/transport/quic/reuse_win.go b/p2p/transport/quic/reuse_win.go index 0f57c8e0ea..14ea1babfa 100644 --- a/p2p/transport/quic/reuse_win.go +++ b/p2p/transport/quic/reuse_win.go @@ -2,14 +2,18 @@ package libp2pquic -import "net" +import ( + "net" + + filter "github.com/libp2p/go-maddr-filter" +) type reuse struct { reuseBase } -func newReuse() (*reuse, error) { - return &reuse{reuseBase: newReuseBase()}, nil +func newReuse(filters *filter.Filters) (*reuse, error) { + return &reuse{reuseBase: newReuseBase(filters)}, nil } func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 6a61eae61e..1754c795d3 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -11,6 +11,7 @@ import ( "github.com/libp2p/go-libp2p-core/pnet" tpt "github.com/libp2p/go-libp2p-core/transport" p2ptls "github.com/libp2p/go-libp2p-tls" + filter "github.com/libp2p/go-maddr-filter" quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" mafmt "github.com/multiformats/go-multiaddr-fmt" @@ -36,12 +37,12 @@ type connManager struct { reuseUDP6 *reuse } -func newConnManager() (*connManager, error) { - reuseUDP4, err := newReuse() +func newConnManager(filters *filter.Filters) (*connManager, error) { + reuseUDP4, err := newReuse(filters) if err != nil { return nil, err } - reuseUDP6, err := newReuse() + reuseUDP6, err := newReuse(filters) if err != nil { return nil, err } @@ -89,7 +90,7 @@ type transport struct { var _ tpt.Transport = &transport{} // NewTransport creates a new QUIC transport -func NewTransport(key ic.PrivKey, psk pnet.PSK) (tpt.Transport, error) { +func NewTransport(key ic.PrivKey, psk pnet.PSK, filters *filter.Filters) (tpt.Transport, error) { if len(psk) > 0 { log.Error("QUIC doesn't support private networks yet.") return nil, errors.New("QUIC doesn't support private networks yet") @@ -102,7 +103,7 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK) (tpt.Transport, error) { if err != nil { return nil, err } - connManager, err := newConnManager() + connManager, err := newConnManager(filters) if err != nil { return nil, err } From bbd82c07899d57911fe152e47e80f9bc850ee7a0 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 16 Mar 2020 14:22:48 +0700 Subject: [PATCH 069/138] use the resolved address for RemoteMultiaddr() --- p2p/transport/quic/transport.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 6a61eae61e..5e3a8b5522 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -121,16 +121,16 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if err != nil { return nil, err } - udpAddr, err := net.ResolveUDPAddr(network, host) + addr, err := net.ResolveUDPAddr(network, host) if err != nil { return nil, err } - addr, err := fromQuicMultiaddr(raddr) + remoteMultiaddr, err := toQuicMultiaddr(addr) if err != nil { return nil, err } tlsConf, keyCh := t.identity.ConfigForPeer(p) - pconn, err := t.connManager.Dial(network, udpAddr) + pconn, err := t.connManager.Dial(network, addr) if err != nil { return nil, err } @@ -167,7 +167,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp localMultiaddr: localMultiaddr, remotePubKey: remotePubKey, remotePeerID: p, - remoteMultiaddr: raddr, + remoteMultiaddr: remoteMultiaddr, }, nil } From 5f2357c2c15912632dd752d78435dd51ca3533d6 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 14 Mar 2020 11:38:25 +0700 Subject: [PATCH 070/138] use a stateless reset key derived from the private key --- p2p/transport/quic/conn_test.go | 56 ++++++++++++++++++++++++++++++--- p2p/transport/quic/listener.go | 2 +- p2p/transport/quic/transport.go | 18 ++++++++++- 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index f7d7253c9c..d70af9f6e4 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -8,12 +8,14 @@ import ( "io/ioutil" mrand "math/rand" "net" + "sync/atomic" "time" ic "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/peer" tpt "github.com/libp2p/go-libp2p-core/transport" filter "github.com/libp2p/go-maddr-filter" + quicproxy "github.com/lucas-clemente/quic-go/integrationtests/tools/proxy" ma "github.com/multiformats/go-multiaddr" . "github.com/onsi/ginkgo" @@ -52,9 +54,9 @@ var _ = Describe("Connection", func() { runServer := func(tr tpt.Transport, multiaddr string) tpt.Listener { addr, err := ma.NewMultiaddr(multiaddr) - Expect(err).ToNot(HaveOccurred()) + ExpectWithOffset(1, err).ToNot(HaveOccurred()) ln, err := tr.Listen(addr) - Expect(err).ToNot(HaveOccurred()) + ExpectWithOffset(1, err).ToNot(HaveOccurred()) return ln } @@ -183,13 +185,13 @@ var _ = Describe("Connection", func() { Expect(err).ToNot(HaveOccurred()) // make sure that connection attempts fails - quicConfig.HandshakeTimeout = 250 * time.Millisecond + clientTransport.(*transport).config.HandshakeTimeout = 250 * time.Millisecond _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).To(HaveOccurred()) Expect(err.(net.Error).Timeout()).To(BeTrue()) // now allow the address and make sure the connection goes through - quicConfig.HandshakeTimeout = 2 * time.Second + clientTransport.(*transport).config.HandshakeTimeout = 2 * time.Second filters.AddFilter(ipNet, filter.ActionAccept) conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) @@ -256,4 +258,50 @@ var _ = Describe("Connection", func() { Eventually(done, 5*time.Second).Should(Receive()) Eventually(done, 5*time.Second).Should(Receive()) }) + + It("sends stateless resets", func() { + serverTransport, err := NewTransport(serverKey, nil, nil) + Expect(err).ToNot(HaveOccurred()) + ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") + + var drop uint32 + serverPort := ln.Addr().(*net.UDPAddr).Port + proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{ + RemoteAddr: fmt.Sprintf("localhost:%d", serverPort), + DropPacket: func(quicproxy.Direction, []byte) bool { + return atomic.LoadUint32(&drop) > 0 + }, + }) + Expect(err).ToNot(HaveOccurred()) + defer proxy.Close() + + // establish a connection + clientTransport, err := NewTransport(clientKey, nil, nil) + Expect(err).ToNot(HaveOccurred()) + proxyAddr, err := toQuicMultiaddr(proxy.LocalAddr()) + Expect(err).ToNot(HaveOccurred()) + conn, err := clientTransport.Dial(context.Background(), proxyAddr, serverID) + Expect(err).ToNot(HaveOccurred()) + str, err := conn.OpenStream() + Expect(err).ToNot(HaveOccurred()) + + // Stop forwarding packets and close the server. + // This prevents the CONNECTION_CLOSE from reaching the client. + atomic.StoreUint32(&drop, 1) + Expect(ln.Close()).To(Succeed()) + time.Sleep(100 * time.Millisecond) // give the kernel some time to free the UDP port + ln = runServer(serverTransport, fmt.Sprintf("/ip4/127.0.0.1/udp/%d/quic", serverPort)) + defer ln.Close() + // Now that the new server is up, re-enable packet forwarding. + atomic.StoreUint32(&drop, 0) + + // The new server doesn't have any state for the previously established connection. + // We expect it to send a stateless reset. + _, rerr := str.Write([]byte("foobar")) + if rerr == nil { + _, rerr = str.Read([]byte{0, 0}) + } + Expect(rerr).To(HaveOccurred()) + Expect(rerr.Error()).To(ContainSubstring("received a stateless reset")) + }) }) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index ed2fb731c3..422f7be96c 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -36,7 +36,7 @@ func newListener(rconn *reuseConn, t *transport, localPeer peer.ID, key ic.PrivK conf, _ := identity.ConfigForAny() return conf, nil } - ln, err := quic.Listen(rconn, &tlsConf, quicConfig) + ln, err := quic.Listen(rconn, &tlsConf, t.config) if err != nil { return nil, err } diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index a60b28f65a..534984abd4 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -2,9 +2,13 @@ package libp2pquic import ( "context" + "crypto/sha256" "errors" + "io" "net" + "golang.org/x/crypto/hkdf" + logging "github.com/ipfs/go-log" ic "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/peer" @@ -85,6 +89,7 @@ type transport struct { localPeer peer.ID identity *p2ptls.Identity connManager *connManager + config *quic.Config } var _ tpt.Transport = &transport{} @@ -107,12 +112,23 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, filters *filter.Filters) (tpt.Tr if err != nil { return nil, err } + config := quicConfig.Clone() + keyBytes, err := key.Raw() + if err != nil { + return nil, err + } + keyReader := hkdf.Expand(sha256.New, keyBytes, []byte("libp2p quic stateless reset key")) + config.StatelessResetKey = make([]byte, 32) + if _, err := io.ReadFull(keyReader, config.StatelessResetKey); err != nil { + return nil, err + } return &transport{ privKey: key, localPeer: localPeer, identity: identity, connManager: connManager, + config: config, }, nil } @@ -135,7 +151,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if err != nil { return nil, err } - sess, err := quic.DialContext(ctx, pconn, addr, host, tlsConf, quicConfig) + sess, err := quic.DialContext(ctx, pconn, addr, host, tlsConf, t.config) if err != nil { pconn.DecreaseCount() return nil, err From 805048ad5378e310a08a063a465bfb90366e9e1f Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 23 Mar 2020 14:10:16 +0700 Subject: [PATCH 071/138] use hkdf.New instead of hkdf.Expand for stateless reset key derivation --- p2p/transport/quic/transport.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 534984abd4..55e7a1bf8d 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -117,7 +117,7 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, filters *filter.Filters) (tpt.Tr if err != nil { return nil, err } - keyReader := hkdf.Expand(sha256.New, keyBytes, []byte("libp2p quic stateless reset key")) + keyReader := hkdf.New(sha256.New, keyBytes, nil, []byte("libp2p quic stateless reset key")) config.StatelessResetKey = make([]byte, 32) if _, err := io.ReadFull(keyReader, config.StatelessResetKey); err != nil { return nil, err From 2559e4ba47cf17eec7f6227f36679a4ba3a8cacc Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 23 Mar 2020 14:17:28 +0700 Subject: [PATCH 072/138] use minio/sha256-simd instead of standard library SHA256 --- p2p/transport/quic/transport.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 55e7a1bf8d..7b3484b203 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -2,11 +2,11 @@ package libp2pquic import ( "context" - "crypto/sha256" "errors" "io" "net" + "github.com/minio/sha256-simd" "golang.org/x/crypto/hkdf" logging "github.com/ipfs/go-log" From 96a91cce5f6b4d2309cd5fd37506b15095727f76 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 25 Mar 2020 16:52:49 +0700 Subject: [PATCH 073/138] move the info used for stateless key expansion to a constant --- p2p/transport/quic/transport.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 7b3484b203..fa54bd8c54 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -36,6 +36,8 @@ var quicConfig = &quic.Config{ KeepAlive: true, } +const statelessResetKeyInfo = "libp2p quic stateless reset key" + type connManager struct { reuseUDP4 *reuse reuseUDP6 *reuse @@ -117,7 +119,7 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, filters *filter.Filters) (tpt.Tr if err != nil { return nil, err } - keyReader := hkdf.New(sha256.New, keyBytes, nil, []byte("libp2p quic stateless reset key")) + keyReader := hkdf.New(sha256.New, keyBytes, nil, []byte(statelessResetKeyInfo)) config.StatelessResetKey = make([]byte, 32) if _, err := io.ReadFull(keyReader, config.StatelessResetKey); err != nil { return nil, err From e8ccc0b534cea5acbaa35680eaa603c5776a2335 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 2 Apr 2020 22:36:47 -0700 Subject: [PATCH 074/138] fix: avoid dialing/listening on dns addresses See https://github.com/libp2p/go-libp2p/issues/841 --- p2p/transport/quic/transport.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index fa54bd8c54..fcb6c82ff1 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -190,9 +190,12 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp }, nil } +// Don't use mafmt.QUIC as we don't want to dial DNS addresses. Just /ip{4,6}/udp/quic +var dialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_UDP), mafmt.Base(ma.P_QUIC)) + // CanDial determines if we can dial to an address func (t *transport) CanDial(addr ma.Multiaddr) bool { - return mafmt.QUIC.Matches(addr) + return dialMatcher.Matches(addr) } // Listen listens for new QUIC connections on the passed multiaddr. From d7e97470198da7c72a252a2dcb97a06260c62cf8 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 16 Mar 2020 18:46:35 +0700 Subject: [PATCH 075/138] test that CanDial fails on /dns addresses --- p2p/transport/quic/transport_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go index 8dfece43ba..111b702c8c 100644 --- a/p2p/transport/quic/transport_test.go +++ b/p2p/transport/quic/transport_test.go @@ -24,6 +24,12 @@ var _ = Describe("Transport", func() { Expect(t.CanDial(validAddr)).To(BeTrue()) }) + It("says that it cannot dial /dns addresses", func() { + addr, err := ma.NewMultiaddr("/dns/google.com/udp/443/quic") + Expect(err).ToNot(HaveOccurred()) + Expect(t.CanDial(addr)).To(BeFalse()) + }) + It("supports the QUIC protocol", func() { protocols := t.Protocols() Expect(protocols).To(HaveLen(1)) From bdbf17697e4484ebea7ab2466e2b1946b68e7e6e Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 26 Mar 2020 21:58:20 +0700 Subject: [PATCH 076/138] export qlogs when the QLOGDIR env variable is set --- p2p/transport/quic/buffered_write_closer.go | 25 ++++++++ .../quic/buffered_write_closer_test.go | 26 ++++++++ p2p/transport/quic/conn_test.go | 4 +- p2p/transport/quic/listener.go | 2 +- p2p/transport/quic/transport.go | 62 +++++++++++++++---- 5 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 p2p/transport/quic/buffered_write_closer.go create mode 100644 p2p/transport/quic/buffered_write_closer_test.go diff --git a/p2p/transport/quic/buffered_write_closer.go b/p2p/transport/quic/buffered_write_closer.go new file mode 100644 index 0000000000..aeeef0035a --- /dev/null +++ b/p2p/transport/quic/buffered_write_closer.go @@ -0,0 +1,25 @@ +package libp2pquic + +import ( + "bufio" + "io" +) + +type bufferedWriteCloser struct { + *bufio.Writer + io.Closer +} + +func newBufferedWriteCloser(writer *bufio.Writer, closer io.Closer) io.WriteCloser { + return &bufferedWriteCloser{ + Writer: writer, + Closer: closer, + } +} + +func (h bufferedWriteCloser) Close() error { + if err := h.Writer.Flush(); err != nil { + return err + } + return h.Closer.Close() +} diff --git a/p2p/transport/quic/buffered_write_closer_test.go b/p2p/transport/quic/buffered_write_closer_test.go new file mode 100644 index 0000000000..949e211c22 --- /dev/null +++ b/p2p/transport/quic/buffered_write_closer_test.go @@ -0,0 +1,26 @@ +package libp2pquic + +import ( + "bufio" + "bytes" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +type nopCloser struct{} + +func (nopCloser) Close() error { return nil } + +var _ = Describe("buffered io.WriteCloser", func() { + It("flushes before closing", func() { + buf := &bytes.Buffer{} + + w := bufio.NewWriter(buf) + wc := newBufferedWriteCloser(w, &nopCloser{}) + wc.Write([]byte("foobar")) + Expect(buf.Len()).To(BeZero()) + Expect(wc.Close()).To(Succeed()) + Expect(buf.String()).To(Equal("foobar")) + }) +}) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index d70af9f6e4..61320343ca 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -185,13 +185,13 @@ var _ = Describe("Connection", func() { Expect(err).ToNot(HaveOccurred()) // make sure that connection attempts fails - clientTransport.(*transport).config.HandshakeTimeout = 250 * time.Millisecond + clientTransport.(*transport).clientConfig.HandshakeTimeout = 250 * time.Millisecond _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).To(HaveOccurred()) Expect(err.(net.Error).Timeout()).To(BeTrue()) // now allow the address and make sure the connection goes through - clientTransport.(*transport).config.HandshakeTimeout = 2 * time.Second + clientTransport.(*transport).clientConfig.HandshakeTimeout = 2 * time.Second filters.AddFilter(ipNet, filter.ActionAccept) conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 422f7be96c..9c77d24bac 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -36,7 +36,7 @@ func newListener(rconn *reuseConn, t *transport, localPeer peer.ID, key ic.PrivK conf, _ := identity.ConfigForAny() return conf, nil } - ln, err := quic.Listen(rconn, &tlsConf, t.config) + ln, err := quic.Listen(rconn, &tlsConf, t.serverConfig) if err != nil { return nil, err } diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index fcb6c82ff1..6707aa64b2 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -1,10 +1,15 @@ package libp2pquic import ( + "bufio" + "compress/gzip" "context" "errors" + "fmt" "io" "net" + "os" + "time" "github.com/minio/sha256-simd" "golang.org/x/crypto/hkdf" @@ -87,11 +92,12 @@ func (c *connManager) Dial(network string, raddr *net.UDPAddr) (*reuseConn, erro // The Transport implements the tpt.Transport interface for QUIC connections. type transport struct { - privKey ic.PrivKey - localPeer peer.ID - identity *p2ptls.Identity - connManager *connManager - config *quic.Config + privKey ic.PrivKey + localPeer peer.ID + identity *p2ptls.Identity + connManager *connManager + serverConfig *quic.Config + clientConfig *quic.Config } var _ tpt.Transport = &transport{} @@ -125,13 +131,17 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, filters *filter.Filters) (tpt.Tr return nil, err } - return &transport{ - privKey: key, - localPeer: localPeer, - identity: identity, - connManager: connManager, - config: config, - }, nil + t := &transport{ + privKey: key, + localPeer: localPeer, + identity: identity, + connManager: connManager, + serverConfig: config, + clientConfig: config.Clone(), + } + t.serverConfig.GetLogWriter = t.GetLogWriterFor("server") + t.clientConfig.GetLogWriter = t.GetLogWriterFor("client") + return t, nil } // Dial dials a new QUIC connection @@ -153,7 +163,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if err != nil { return nil, err } - sess, err := quic.DialContext(ctx, pconn, addr, host, tlsConf, t.config) + sess, err := quic.DialContext(ctx, pconn, addr, host, tlsConf, t.clientConfig) if err != nil { pconn.DecreaseCount() return nil, err @@ -190,6 +200,32 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp }, nil } +func (t *transport) GetLogWriterFor(role string) func([]byte) io.WriteCloser { + qlogDir := os.Getenv("QLOGDIR") + if len(qlogDir) == 0 { + return nil + } + return func(connID []byte) io.WriteCloser { + // create the QLOGDIR, if it doesn't exist + if _, err := os.Stat(qlogDir); os.IsNotExist(err) { + if err := os.MkdirAll(qlogDir, 0777); err != nil { + log.Errorf("creating the QLOGDIR failed: %s", err) + return nil + } + } + now := time.Now() + t := fmt.Sprintf("%d-%02d-%02dT%02d-%02d-%02d-%06d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond()/1000) + filename := fmt.Sprintf("%s/log_%s_%s_%x.qlog.gz", qlogDir, t, role, connID) + f, err := os.Create(filename) + if err != nil { + log.Errorf("unable to create qlog file %s: %s", filename, err) + return nil + } + gz := gzip.NewWriter(f) + return newBufferedWriteCloser(bufio.NewWriter(gz), gz) + } +} + // Don't use mafmt.QUIC as we don't want to dial DNS addresses. Just /ip{4,6}/udp/quic var dialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_UDP), mafmt.Base(ma.P_QUIC)) From 6e90057f87afef38295631521a8450c4009e028e Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 6 Apr 2020 12:32:53 +0700 Subject: [PATCH 077/138] use RFC3339Nano date format for qlog file names --- p2p/transport/quic/transport.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 6707aa64b2..7c9b959797 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -213,8 +213,7 @@ func (t *transport) GetLogWriterFor(role string) func([]byte) io.WriteCloser { return nil } } - now := time.Now() - t := fmt.Sprintf("%d-%02d-%02dT%02d-%02d-%02d-%06d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond()/1000) + t := time.Now().Format(time.RFC3339Nano) filename := fmt.Sprintf("%s/log_%s_%s_%x.qlog.gz", qlogDir, t, role, connID) f, err := os.Create(filename) if err != nil { From 4710507714496fea3a6f03727ccb3573f5d509e2 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 6 Apr 2020 12:34:16 +0700 Subject: [PATCH 078/138] simplify the mkdir for the QLOGDIR --- p2p/transport/quic/transport.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 7c9b959797..059b9f3685 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -207,11 +207,9 @@ func (t *transport) GetLogWriterFor(role string) func([]byte) io.WriteCloser { } return func(connID []byte) io.WriteCloser { // create the QLOGDIR, if it doesn't exist - if _, err := os.Stat(qlogDir); os.IsNotExist(err) { - if err := os.MkdirAll(qlogDir, 0777); err != nil { - log.Errorf("creating the QLOGDIR failed: %s", err) - return nil - } + if err := os.MkdirAll(qlogDir, 0777); err != nil { + log.Errorf("creating the QLOGDIR failed: %s", err) + return nil } t := time.Now().Format(time.RFC3339Nano) filename := fmt.Sprintf("%s/log_%s_%s_%x.qlog.gz", qlogDir, t, role, connID) From 3ec3559c03b70344c07e471a7656eaa824126136 Mon Sep 17 00:00:00 2001 From: Will Date: Tue, 7 Apr 2020 07:51:20 -0700 Subject: [PATCH 079/138] switch local route binding to use netroute (#134) switch local route binding to use netroute --- p2p/transport/quic/libp2pquic_suite_test.go | 2 +- p2p/transport/quic/netlink_linux.go | 10 --- p2p/transport/quic/netlink_other.go | 9 --- .../quic/{reuse_base.go => reuse.go} | 39 ++++++++--- p2p/transport/quic/reuse_linux_test.go | 42 ------------ p2p/transport/quic/reuse_not_win.go | 68 ------------------- p2p/transport/quic/reuse_test.go | 33 ++++++++- p2p/transport/quic/reuse_win.go | 30 -------- p2p/transport/quic/transport.go | 11 +-- 9 files changed, 64 insertions(+), 180 deletions(-) delete mode 100644 p2p/transport/quic/netlink_linux.go delete mode 100644 p2p/transport/quic/netlink_other.go rename p2p/transport/quic/{reuse_base.go => reuse.go} (81%) delete mode 100644 p2p/transport/quic/reuse_linux_test.go delete mode 100644 p2p/transport/quic/reuse_not_win.go delete mode 100644 p2p/transport/quic/reuse_win.go diff --git a/p2p/transport/quic/libp2pquic_suite_test.go b/p2p/transport/quic/libp2pquic_suite_test.go index d6ae1510fd..5905763c07 100644 --- a/p2p/transport/quic/libp2pquic_suite_test.go +++ b/p2p/transport/quic/libp2pquic_suite_test.go @@ -32,7 +32,7 @@ var ( func isGarbageCollectorRunning() bool { var b bytes.Buffer pprof.Lookup("goroutine").WriteTo(&b, 1) - return strings.Contains(b.String(), "go-libp2p-quic-transport.(*reuseBase).runGarbageCollector") + return strings.Contains(b.String(), "go-libp2p-quic-transport.(*reuse).runGarbageCollector") } var _ = BeforeEach(func() { diff --git a/p2p/transport/quic/netlink_linux.go b/p2p/transport/quic/netlink_linux.go deleted file mode 100644 index a5f6ffe6e1..0000000000 --- a/p2p/transport/quic/netlink_linux.go +++ /dev/null @@ -1,10 +0,0 @@ -// +build linux - -package libp2pquic - -import "golang.org/x/sys/unix" - -// We just need netlink_route here. -// note: We should avoid the use of netlink_xfrm or netlink_netfilter has it is -// not allowed by Android in his base policy. -var SupportedNlFamilies = []int{unix.NETLINK_ROUTE} diff --git a/p2p/transport/quic/netlink_other.go b/p2p/transport/quic/netlink_other.go deleted file mode 100644 index ccc073da0b..0000000000 --- a/p2p/transport/quic/netlink_other.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build !linux -// +build !windows - -package libp2pquic - -import "github.com/vishvananda/netlink/nl" - -// SupportedNlFamilies is the default netlink families used by the netlink package -var SupportedNlFamilies = nl.SupportedNlFamilies diff --git a/p2p/transport/quic/reuse_base.go b/p2p/transport/quic/reuse.go similarity index 81% rename from p2p/transport/quic/reuse_base.go rename to p2p/transport/quic/reuse.go index 053b777395..f566de2fd0 100644 --- a/p2p/transport/quic/reuse_base.go +++ b/p2p/transport/quic/reuse.go @@ -6,6 +6,7 @@ import ( "time" filter "github.com/libp2p/go-maddr-filter" + "github.com/libp2p/go-netroute" ) // Constant. Defined as variables to simplify testing. @@ -51,7 +52,7 @@ func (c *reuseConn) ShouldGarbageCollect(now time.Time) bool { return !c.unusedSince.IsZero() && c.unusedSince.Add(maxUnusedDuration).Before(now) } -type reuseBase struct { +type reuse struct { mutex sync.Mutex filters *filter.Filters @@ -63,15 +64,15 @@ type reuseBase struct { global map[int]*reuseConn } -func newReuseBase(filters *filter.Filters) reuseBase { - return reuseBase{ +func newReuse(filters *filter.Filters) *reuse { + return &reuse{ filters: filters, unicast: make(map[string]map[int]*reuseConn), global: make(map[int]*reuseConn), } } -func (r *reuseBase) runGarbageCollector() { +func (r *reuse) runGarbageCollector() { ticker := time.NewTicker(garbageCollectInterval) defer ticker.Stop() @@ -110,17 +111,37 @@ func (r *reuseBase) runGarbageCollector() { } // must be called while holding the mutex -func (r *reuseBase) maybeStartGarbageCollector() { +func (r *reuse) maybeStartGarbageCollector() { if !r.garbageCollectorRunning { r.garbageCollectorRunning = true go r.runGarbageCollector() } } +func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { + var ip *net.IP + if router, err := netroute.New(); err == nil { + _, _, src, err := router.Route(raddr.IP) + if err == nil && !src.IsUnspecified() { + ip = &src + } + } + + r.mutex.Lock() + defer r.mutex.Unlock() + + conn, err := r.dialLocked(network, raddr, ip) + if err != nil { + return nil, err + } + conn.IncreaseCount() + r.maybeStartGarbageCollector() + return conn, nil +} -func (r *reuseBase) dialLocked(network string, raddr *net.UDPAddr, ips []net.IP) (*reuseConn, error) { - for _, ip := range ips { +func (r *reuse) dialLocked(network string, raddr *net.UDPAddr, source *net.IP) (*reuseConn, error) { + if source != nil { // We already have at least one suitable connection... - if conns, ok := r.unicast[ip.String()]; ok { + if conns, ok := r.unicast[source.String()]; ok { // ... we don't care which port we're dialing from. Just use the first. for _, c := range conns { return c, nil @@ -152,7 +173,7 @@ func (r *reuseBase) dialLocked(network string, raddr *net.UDPAddr, ips []net.IP) return rconn, nil } -func (r *reuseBase) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { +func (r *reuse) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { conn, err := net.ListenUDP(network, laddr) if err != nil { return nil, err diff --git a/p2p/transport/quic/reuse_linux_test.go b/p2p/transport/quic/reuse_linux_test.go deleted file mode 100644 index 6fe77dbbf6..0000000000 --- a/p2p/transport/quic/reuse_linux_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// +build linux - -package libp2pquic - -import ( - "net" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -var _ = Describe("Reuse (on Linux)", func() { - var reuse *reuse - - BeforeEach(func() { - var err error - reuse, err = newReuse(nil) - Expect(err).ToNot(HaveOccurred()) - }) - - Context("creating and reusing connections", func() { - AfterEach(func() { closeAllConns(reuse) }) - - It("reuses a connection it created for listening on a specific interface", func() { - raddr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") - Expect(err).ToNot(HaveOccurred()) - ips, err := reuse.getSourceIPs("udp4", raddr) - Expect(err).ToNot(HaveOccurred()) - Expect(ips).ToNot(BeEmpty()) - // listen - addr, err := net.ResolveUDPAddr("udp4", ips[0].String()+":0") - Expect(err).ToNot(HaveOccurred()) - lconn, err := reuse.Listen("udp4", addr) - Expect(err).ToNot(HaveOccurred()) - Expect(lconn.GetCount()).To(Equal(1)) - // dial - conn, err := reuse.Dial("udp4", raddr) - Expect(err).ToNot(HaveOccurred()) - Expect(conn.GetCount()).To(Equal(2)) - }) - }) -}) diff --git a/p2p/transport/quic/reuse_not_win.go b/p2p/transport/quic/reuse_not_win.go deleted file mode 100644 index 57097a3098..0000000000 --- a/p2p/transport/quic/reuse_not_win.go +++ /dev/null @@ -1,68 +0,0 @@ -// +build !windows - -package libp2pquic - -import ( - "net" - - filter "github.com/libp2p/go-maddr-filter" - - "github.com/vishvananda/netlink" -) - -type reuse struct { - reuseBase - - handle *netlink.Handle // Only set on Linux. nil on other systems. -} - -func newReuse(filters *filter.Filters) (*reuse, error) { - handle, err := netlink.NewHandle(SupportedNlFamilies...) - if err == netlink.ErrNotImplemented { - handle = nil - } else if err != nil { - return nil, err - } - return &reuse{ - reuseBase: newReuseBase(filters), - handle: handle, - }, nil -} - -// Get the source IP that the kernel would use for dialing. -// This only works on Linux. -// On other systems, this returns an empty slice of IP addresses. -func (r *reuse) getSourceIPs(network string, raddr *net.UDPAddr) ([]net.IP, error) { - if r.handle == nil { - return nil, nil - } - - routes, err := r.handle.RouteGet(raddr.IP) - if err != nil { - return nil, err - } - - ips := make([]net.IP, 0, len(routes)) - for _, route := range routes { - ips = append(ips, route.Src) - } - return ips, nil -} - -func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { - ips, err := r.getSourceIPs(network, raddr) - if err != nil { - return nil, err - } - - r.mutex.Lock() - defer r.mutex.Unlock() - - conn, err := r.dialLocked(network, raddr, ips) - if err != nil { - return nil, err - } - conn.IncreaseCount() - r.maybeStartGarbageCollector() - return conn, nil -} diff --git a/p2p/transport/quic/reuse_test.go b/p2p/transport/quic/reuse_test.go index 6a24d5d575..1072bd00be 100644 --- a/p2p/transport/quic/reuse_test.go +++ b/p2p/transport/quic/reuse_test.go @@ -4,6 +4,7 @@ import ( "net" "time" + "github.com/libp2p/go-netroute" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -32,13 +33,19 @@ func closeAllConns(reuse *reuse) { Eventually(isGarbageCollectorRunning).Should(BeFalse()) } +func OnPlatformsWithRoutingTablesIt(description string, f interface{}) { + if _, err := netroute.New(); err == nil { + It(description, f) + } else { + PIt(description, f) + } +} + var _ = Describe("Reuse", func() { var reuse *reuse BeforeEach(func() { - var err error - reuse, err = newReuse(nil) - Expect(err).ToNot(HaveOccurred()) + reuse = newReuse(nil) }) Context("creating and reusing connections", func() { @@ -85,6 +92,26 @@ var _ = Describe("Reuse", func() { Expect(err).ToNot(HaveOccurred()) Expect(conn.GetCount()).To(Equal(2)) }) + + OnPlatformsWithRoutingTablesIt("reuses a connection it created for listening on a specific interface", func() { + router, err := netroute.New() + Expect(err).ToNot(HaveOccurred()) + + raddr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") + Expect(err).ToNot(HaveOccurred()) + _, _, ip, err := router.Route(raddr.IP) + Expect(err).ToNot(HaveOccurred()) + // listen + addr, err := net.ResolveUDPAddr("udp4", ip.String()+":0") + Expect(err).ToNot(HaveOccurred()) + lconn, err := reuse.Listen("udp4", addr) + Expect(err).ToNot(HaveOccurred()) + Expect(lconn.GetCount()).To(Equal(1)) + // dial + conn, err := reuse.Dial("udp4", raddr) + Expect(err).ToNot(HaveOccurred()) + Expect(conn.GetCount()).To(Equal(2)) + }) }) Context("garbage-collecting connections", func() { diff --git a/p2p/transport/quic/reuse_win.go b/p2p/transport/quic/reuse_win.go deleted file mode 100644 index 14ea1babfa..0000000000 --- a/p2p/transport/quic/reuse_win.go +++ /dev/null @@ -1,30 +0,0 @@ -// +build windows - -package libp2pquic - -import ( - "net" - - filter "github.com/libp2p/go-maddr-filter" -) - -type reuse struct { - reuseBase -} - -func newReuse(filters *filter.Filters) (*reuse, error) { - return &reuse{reuseBase: newReuseBase(filters)}, nil -} - -func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { - r.mutex.Lock() - defer r.mutex.Unlock() - - conn, err := r.dialLocked(network, raddr, nil) - if err != nil { - return nil, err - } - conn.IncreaseCount() - r.maybeStartGarbageCollector() - return conn, nil -} diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 059b9f3685..97add03c07 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -49,14 +49,9 @@ type connManager struct { } func newConnManager(filters *filter.Filters) (*connManager, error) { - reuseUDP4, err := newReuse(filters) - if err != nil { - return nil, err - } - reuseUDP6, err := newReuse(filters) - if err != nil { - return nil, err - } + reuseUDP4 := newReuse(filters) + reuseUDP6 := newReuse(filters) + return &connManager{ reuseUDP4: reuseUDP4, reuseUDP6: reuseUDP6, From 31d6fba8368ed7ebc8db8146916a1f38264ac3ab Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 16 Apr 2020 12:19:29 +0700 Subject: [PATCH 080/138] write qlogs to a temporary file first, then rename them when done --- p2p/transport/quic/buffered_write_closer.go | 25 ------ .../quic/buffered_write_closer_test.go | 26 ------ p2p/transport/quic/qlog.go | 83 +++++++++++++++++++ p2p/transport/quic/qlog_test.go | 83 +++++++++++++++++++ p2p/transport/quic/transport.go | 32 +------ 5 files changed, 168 insertions(+), 81 deletions(-) delete mode 100644 p2p/transport/quic/buffered_write_closer.go delete mode 100644 p2p/transport/quic/buffered_write_closer_test.go create mode 100644 p2p/transport/quic/qlog.go create mode 100644 p2p/transport/quic/qlog_test.go diff --git a/p2p/transport/quic/buffered_write_closer.go b/p2p/transport/quic/buffered_write_closer.go deleted file mode 100644 index aeeef0035a..0000000000 --- a/p2p/transport/quic/buffered_write_closer.go +++ /dev/null @@ -1,25 +0,0 @@ -package libp2pquic - -import ( - "bufio" - "io" -) - -type bufferedWriteCloser struct { - *bufio.Writer - io.Closer -} - -func newBufferedWriteCloser(writer *bufio.Writer, closer io.Closer) io.WriteCloser { - return &bufferedWriteCloser{ - Writer: writer, - Closer: closer, - } -} - -func (h bufferedWriteCloser) Close() error { - if err := h.Writer.Flush(); err != nil { - return err - } - return h.Closer.Close() -} diff --git a/p2p/transport/quic/buffered_write_closer_test.go b/p2p/transport/quic/buffered_write_closer_test.go deleted file mode 100644 index 949e211c22..0000000000 --- a/p2p/transport/quic/buffered_write_closer_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package libp2pquic - -import ( - "bufio" - "bytes" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -type nopCloser struct{} - -func (nopCloser) Close() error { return nil } - -var _ = Describe("buffered io.WriteCloser", func() { - It("flushes before closing", func() { - buf := &bytes.Buffer{} - - w := bufio.NewWriter(buf) - wc := newBufferedWriteCloser(w, &nopCloser{}) - wc.Write([]byte("foobar")) - Expect(buf.Len()).To(BeZero()) - Expect(wc.Close()).To(Succeed()) - Expect(buf.String()).To(Equal("foobar")) - }) -}) diff --git a/p2p/transport/quic/qlog.go b/p2p/transport/quic/qlog.go new file mode 100644 index 0000000000..73c9a4e6e4 --- /dev/null +++ b/p2p/transport/quic/qlog.go @@ -0,0 +1,83 @@ +package libp2pquic + +import ( + "bufio" + "compress/gzip" + "fmt" + "io" + "os" + "time" +) + +var qlogDir string + +func init() { + qlogDir = os.Getenv("QLOGDIR") +} + +func getLogWriterFor(role string) func([]byte) io.WriteCloser { + if len(qlogDir) == 0 { + return nil + } + return func(connID []byte) io.WriteCloser { + // create the QLOGDIR, if it doesn't exist + if err := os.MkdirAll(qlogDir, 0777); err != nil { + log.Errorf("creating the QLOGDIR failed: %s", err) + return nil + } + return newQlogger(role, connID) + } +} + +type qlogger struct { + f *os.File // QLOGDIR/.log_xxx.qlog.gz.swp + filename string // QLOGDIR/log_xxx.qlog.gz + io.WriteCloser +} + +func newQlogger(role string, connID []byte) io.WriteCloser { + t := time.Now().UTC().Format("2006-01-02T15-04-05.999999999UTC") + finalFilename := fmt.Sprintf("%s%clog_%s_%s_%x.qlog.gz", qlogDir, os.PathSeparator, t, role, connID) + filename := fmt.Sprintf("%s%c.log_%s_%s_%x.qlog.gz.swp", qlogDir, os.PathSeparator, t, role, connID) + f, err := os.Create(filename) + if err != nil { + log.Errorf("unable to create qlog file %s: %s", filename, err) + return nil + } + gz := gzip.NewWriter(f) + return &qlogger{ + f: f, + filename: finalFilename, + WriteCloser: newBufferedWriteCloser(bufio.NewWriter(gz), gz), + } +} + +func (l *qlogger) Close() error { + if err := l.WriteCloser.Close(); err != nil { + return err + } + path := l.f.Name() + if err := l.f.Close(); err != nil { + return err + } + return os.Rename(path, l.filename) +} + +type bufferedWriteCloser struct { + *bufio.Writer + io.Closer +} + +func newBufferedWriteCloser(writer *bufio.Writer, closer io.Closer) io.WriteCloser { + return &bufferedWriteCloser{ + Writer: writer, + Closer: closer, + } +} + +func (h bufferedWriteCloser) Close() error { + if err := h.Writer.Flush(); err != nil { + return err + } + return h.Closer.Close() +} diff --git a/p2p/transport/quic/qlog_test.go b/p2p/transport/quic/qlog_test.go new file mode 100644 index 0000000000..dc28d50bd8 --- /dev/null +++ b/p2p/transport/quic/qlog_test.go @@ -0,0 +1,83 @@ +package libp2pquic + +import ( + "bytes" + "compress/gzip" + "fmt" + "io/ioutil" + "os" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +type nopCloser struct{} + +func (nopCloser) Close() error { return nil } + +var _ = Describe("qlogger", func() { + var origQlogDir string + + BeforeEach(func() { + origQlogDir = qlogDir + d, err := ioutil.TempDir("", "libp2p-quic-transport-test") + Expect(err).ToNot(HaveOccurred()) + fmt.Fprintf(GinkgoWriter, "Creating temporary directory: %s\n", d) + qlogDir = d + }) + + AfterEach(func() { + qlogDir = origQlogDir + }) + + getFile := func() os.FileInfo { + files, err := ioutil.ReadDir(qlogDir) + Expect(err).ToNot(HaveOccurred()) + Expect(files).To(HaveLen(1)) + return files[0] + } + + It("saves a qlog", func() { + logger := newQlogger("server", []byte{0xde, 0xad, 0xbe, 0xef}) + file := getFile() + Expect(string(file.Name()[0])).To(Equal(".")) + Expect(file.Name()).To(HaveSuffix(".qlog.gz.swp")) + // close the logger. This should move the file. + Expect(logger.Close()).To(Succeed()) + file = getFile() + Expect(string(file.Name()[0])).ToNot(Equal(".")) + Expect(file.Name()).To(HaveSuffix(".qlog.gz")) + Expect(file.Name()).To(And( + ContainSubstring("server"), + ContainSubstring("deadbeef"), + )) + }) + + It("buffers", func() { + logger := newQlogger("server", []byte("connid")) + initialSize := getFile().Size() + // Do a small write. + // Since the writter is buffered, this should not be written to disk yet. + logger.Write([]byte("foobar")) + Expect(getFile().Size()).To(Equal(initialSize)) + // Close the logger. This should flush the buffer to disk. + Expect(logger.Close()).To(Succeed()) + finalSize := getFile().Size() + fmt.Fprintf(GinkgoWriter, "initial log file size: %d, final log file size: %d\n", initialSize, finalSize) + Expect(finalSize).To(BeNumerically(">", initialSize)) + }) + + It("compresses", func() { + logger := newQlogger("server", []byte("connid")) + logger.Write([]byte("foobar")) + Expect(logger.Close()).To(Succeed()) + compressed, err := ioutil.ReadFile(qlogDir + "/" + getFile().Name()) + Expect(err).ToNot(HaveOccurred()) + Expect(compressed).ToNot(Equal("foobar")) + gz, err := gzip.NewReader(bytes.NewReader(compressed)) + Expect(err).ToNot(HaveOccurred()) + data, err := ioutil.ReadAll(gz) + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal([]byte("foobar"))) + }) +}) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 97add03c07..d71b366d7f 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -1,15 +1,10 @@ package libp2pquic import ( - "bufio" - "compress/gzip" "context" "errors" - "fmt" "io" "net" - "os" - "time" "github.com/minio/sha256-simd" "golang.org/x/crypto/hkdf" @@ -134,8 +129,8 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, filters *filter.Filters) (tpt.Tr serverConfig: config, clientConfig: config.Clone(), } - t.serverConfig.GetLogWriter = t.GetLogWriterFor("server") - t.clientConfig.GetLogWriter = t.GetLogWriterFor("client") + t.serverConfig.GetLogWriter = getLogWriterFor("server") + t.clientConfig.GetLogWriter = getLogWriterFor("client") return t, nil } @@ -195,29 +190,6 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp }, nil } -func (t *transport) GetLogWriterFor(role string) func([]byte) io.WriteCloser { - qlogDir := os.Getenv("QLOGDIR") - if len(qlogDir) == 0 { - return nil - } - return func(connID []byte) io.WriteCloser { - // create the QLOGDIR, if it doesn't exist - if err := os.MkdirAll(qlogDir, 0777); err != nil { - log.Errorf("creating the QLOGDIR failed: %s", err) - return nil - } - t := time.Now().Format(time.RFC3339Nano) - filename := fmt.Sprintf("%s/log_%s_%s_%x.qlog.gz", qlogDir, t, role, connID) - f, err := os.Create(filename) - if err != nil { - log.Errorf("unable to create qlog file %s: %s", filename, err) - return nil - } - gz := gzip.NewWriter(f) - return newBufferedWriteCloser(bufio.NewWriter(gz), gz) - } -} - // Don't use mafmt.QUIC as we don't want to dial DNS addresses. Just /ip{4,6}/udp/quic var dialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_UDP), mafmt.Base(ma.P_QUIC)) From 9b04f0ff285b6fb1747b43ba025481e5a97c8f18 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 20 Apr 2020 17:16:28 +0700 Subject: [PATCH 081/138] add command line client and server --- p2p/transport/quic/cmd/client/main.go | 69 +++++++++++++++++++++++ p2p/transport/quic/cmd/server/main.go | 79 +++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 p2p/transport/quic/cmd/client/main.go create mode 100644 p2p/transport/quic/cmd/server/main.go diff --git a/p2p/transport/quic/cmd/client/main.go b/p2p/transport/quic/cmd/client/main.go new file mode 100644 index 0000000000..8d391c9786 --- /dev/null +++ b/p2p/transport/quic/cmd/client/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "context" + "crypto/rand" + "fmt" + "io/ioutil" + "log" + "os" + + ic "github.com/libp2p/go-libp2p-core/crypto" + peer "github.com/libp2p/go-libp2p-core/peer" + libp2pquic "github.com/libp2p/go-libp2p-quic-transport" + ma "github.com/multiformats/go-multiaddr" +) + +func main() { + if len(os.Args) != 3 { + fmt.Printf("Usage: ./main ") + return + } + if err := run(os.Args[1], os.Args[2]); err != nil { + log.Fatalf(err.Error()) + } +} + +func run(raddr string, p string) error { + peerID, err := peer.Decode(p) + if err != nil { + return err + } + addr, err := ma.NewMultiaddr(raddr) + if err != nil { + return err + } + priv, _, err := ic.GenerateECDSAKeyPair(rand.Reader) + if err != nil { + return err + } + + t, err := libp2pquic.NewTransport(priv, nil, nil) + if err != nil { + return err + } + + log.Printf("Dialing %s\n", addr.String()) + conn, err := t.Dial(context.Background(), addr, peerID) + if err != nil { + return err + } + str, err := conn.OpenStream() + if err != nil { + return err + } + const msg = "Hello world!" + log.Printf("Sending: %s\n", msg) + if _, err := str.Write([]byte(msg)); err != nil { + return err + } + if err := str.Close(); err != nil { + return err + } + data, err := ioutil.ReadAll(str) + if err != nil { + return err + } + log.Printf("Received: %s\n", data) + return conn.Close() +} diff --git a/p2p/transport/quic/cmd/server/main.go b/p2p/transport/quic/cmd/server/main.go new file mode 100644 index 0000000000..68eda3de6a --- /dev/null +++ b/p2p/transport/quic/cmd/server/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "crypto/rand" + "fmt" + "io/ioutil" + "log" + "os" + + ic "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + tpt "github.com/libp2p/go-libp2p-core/transport" + libp2pquic "github.com/libp2p/go-libp2p-quic-transport" + ma "github.com/multiformats/go-multiaddr" +) + +func main() { + if len(os.Args) != 2 { + fmt.Printf("Usage: ./main ") + return + } + if err := run(os.Args[1]); err != nil { + log.Fatalf(err.Error()) + } +} + +func run(port string) error { + addr, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/udp/%s/quic", port)) + if err != nil { + return err + } + priv, _, err := ic.GenerateECDSAKeyPair(rand.Reader) + if err != nil { + return err + } + peerID, err := peer.IDFromPrivateKey(priv) + if err != nil { + return err + } + + t, err := libp2pquic.NewTransport(priv, nil, nil) + if err != nil { + return err + } + + ln, err := t.Listen(addr) + if err != nil { + return err + } + fmt.Printf("Listening. Now run: go run cmd/client/main.go %s %s\n", ln.Multiaddr(), peerID) + for { + conn, err := ln.Accept() + if err != nil { + return err + } + log.Printf("Accepted new connection from %s (%s)\n", conn.RemotePeer(), conn.RemoteMultiaddr()) + go func() { + if err := handleConn(conn); err != nil { + log.Printf("handling conn failed: %s", err.Error()) + } + }() + } +} + +func handleConn(conn tpt.CapableConn) error { + str, err := conn.AcceptStream() + if err != nil { + return err + } + data, err := ioutil.ReadAll(str) + if err != nil { + return err + } + log.Printf("Received: %s\n", data) + if _, err := str.Write([]byte(data)); err != nil { + return err + } + return str.Close() +} From 82c90ce038b3b90d45d950023fccefdb45b219c3 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 21 Apr 2020 10:20:35 +0700 Subject: [PATCH 082/138] apply @Stebalien's suggestions from code review Co-Authored-By: Steven Allen --- p2p/transport/quic/cmd/client/main.go | 2 +- p2p/transport/quic/cmd/server/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/p2p/transport/quic/cmd/client/main.go b/p2p/transport/quic/cmd/client/main.go index 8d391c9786..37ede0c338 100644 --- a/p2p/transport/quic/cmd/client/main.go +++ b/p2p/transport/quic/cmd/client/main.go @@ -16,7 +16,7 @@ import ( func main() { if len(os.Args) != 3 { - fmt.Printf("Usage: ./main ") + fmt.Printf("Usage: %s ", os.Args[0]) return } if err := run(os.Args[1], os.Args[2]); err != nil { diff --git a/p2p/transport/quic/cmd/server/main.go b/p2p/transport/quic/cmd/server/main.go index 68eda3de6a..3a5fe24281 100644 --- a/p2p/transport/quic/cmd/server/main.go +++ b/p2p/transport/quic/cmd/server/main.go @@ -16,7 +16,7 @@ import ( func main() { if len(os.Args) != 2 { - fmt.Printf("Usage: ./main ") + fmt.Printf("Usage: %s ", os.Args[0]) return } if err := run(os.Args[1]); err != nil { From 4df1224724fd9d9300fcadcddaa1b8eeedbe4d98 Mon Sep 17 00:00:00 2001 From: Aarsh Shah Date: Tue, 19 May 2020 17:18:36 +0530 Subject: [PATCH 083/138] gate QUIC connections via new ConnectionGater (#152) --- p2p/transport/quic/conn_test.go | 78 +++++++++++++++++++++++++---- p2p/transport/quic/filtered_conn.go | 36 ++++++++++--- p2p/transport/quic/listener.go | 10 ++++ p2p/transport/quic/reuse.go | 19 +++---- p2p/transport/quic/transport.go | 24 ++++++--- 5 files changed, 135 insertions(+), 32 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 61320343ca..7847c28f2f 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -8,20 +8,54 @@ import ( "io/ioutil" mrand "math/rand" "net" + "sync" "sync/atomic" "time" + "github.com/libp2p/go-libp2p-core/control" ic "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" tpt "github.com/libp2p/go-libp2p-core/transport" - filter "github.com/libp2p/go-maddr-filter" + quicproxy "github.com/lucas-clemente/quic-go/integrationtests/tools/proxy" ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) +type mockGater struct { + lk sync.Mutex + acceptAll bool + blockedPeer peer.ID +} + +func (c *mockGater) InterceptAccept(addrs network.ConnMultiaddrs) bool { + c.lk.Lock() + defer c.lk.Unlock() + return c.acceptAll || !manet.IsIPLoopback(addrs.RemoteMultiaddr()) +} + +func (c *mockGater) InterceptPeerDial(p peer.ID) (allow bool) { + return true +} + +func (c *mockGater) InterceptAddrDial(peer.ID, ma.Multiaddr) (allow bool) { + return true +} + +func (c *mockGater) InterceptSecured(_ network.Direction, p peer.ID, _ network.ConnMultiaddrs) (allow bool) { + c.lk.Lock() + defer c.lk.Unlock() + return !(p == c.blockedPeer) +} + +func (c *mockGater) InterceptUpgraded(network.Conn) (allow bool, reason control.DisconnectReason) { + return true, 0 +} + var _ = Describe("Connection", func() { var ( serverKey, clientKey ic.PrivKey @@ -165,18 +199,13 @@ var _ = Describe("Connection", func() { Eventually(done).Should(BeClosed()) }) - It("filters addresses", func() { - filters := filter.NewFilters() - ipNet := net.IPNet{ - IP: net.IPv4(127, 0, 0, 1), - Mask: net.IPv4Mask(255, 255, 255, 255), - } - filters.AddFilter(ipNet, filter.ActionDeny) + It("gates accepted connections", func() { testMA, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234/quic") Expect(err).ToNot(HaveOccurred()) - Expect(filters.AddrBlocked(testMA)).To(BeTrue()) + cg := &mockGater{} + Expect(cg.InterceptAccept(&connAddrs{rmAddr: testMA})).To(BeFalse()) - serverTransport, err := NewTransport(serverKey, nil, filters) + serverTransport, err := NewTransport(serverKey, nil, cg) Expect(err).ToNot(HaveOccurred()) ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() @@ -192,7 +221,34 @@ var _ = Describe("Connection", func() { // now allow the address and make sure the connection goes through clientTransport.(*transport).clientConfig.HandshakeTimeout = 2 * time.Second - filters.AddFilter(ipNet, filter.ActionAccept) + cg.lk.Lock() + cg.acceptAll = true + cg.lk.Unlock() + conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) + Expect(err).ToNot(HaveOccurred()) + conn.Close() + }) + + It("gates secured connections", func() { + serverTransport, err := NewTransport(serverKey, nil, nil) + Expect(err).ToNot(HaveOccurred()) + ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") + defer ln.Close() + + cg := &mockGater{acceptAll: true, blockedPeer: serverID} + clientTransport, err := NewTransport(clientKey, nil, cg) + Expect(err).ToNot(HaveOccurred()) + + // make sure that connection attempts fails + clientTransport.(*transport).clientConfig.HandshakeTimeout = 250 * time.Millisecond + _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) + Expect(err).To(HaveOccurred()) + + // now allow the peerId and make sure the connection goes through + clientTransport.(*transport).clientConfig.HandshakeTimeout = 2 * time.Second + cg.lk.Lock() + cg.blockedPeer = "none" + cg.lk.Unlock() conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) conn.Close() diff --git a/p2p/transport/quic/filtered_conn.go b/p2p/transport/quic/filtered_conn.go index dc60bb08e2..c5f046edab 100644 --- a/p2p/transport/quic/filtered_conn.go +++ b/p2p/transport/quic/filtered_conn.go @@ -3,17 +3,38 @@ package libp2pquic import ( "net" - filter "github.com/libp2p/go-maddr-filter" + "github.com/libp2p/go-libp2p-core/connmgr" + + ma "github.com/multiformats/go-multiaddr" ) +type connAddrs struct { + lmAddr ma.Multiaddr + rmAddr ma.Multiaddr +} + +func (c *connAddrs) LocalMultiaddr() ma.Multiaddr { + return c.lmAddr +} + +func (c *connAddrs) RemoteMultiaddr() ma.Multiaddr { + return c.rmAddr +} + type filteredConn struct { net.PacketConn - filters *filter.Filters + lmAddr ma.Multiaddr + gater connmgr.ConnectionGater } -func newFilteredConn(c net.PacketConn, filters *filter.Filters) net.PacketConn { - return &filteredConn{PacketConn: c, filters: filters} +func newFilteredConn(c net.PacketConn, gater connmgr.ConnectionGater) net.PacketConn { + lmAddr, err := toQuicMultiaddr(c.LocalAddr()) + if err != nil { + panic(err) + } + + return &filteredConn{PacketConn: c, gater: gater, lmAddr: lmAddr} } func (c *filteredConn) ReadFrom(b []byte) (n int, addr net.Addr, rerr error) { @@ -23,11 +44,14 @@ func (c *filteredConn) ReadFrom(b []byte) (n int, addr net.Addr, rerr error) { if n < 1 || b[0]&0x80 == 0 { return } - maddr, err := toQuicMultiaddr(addr) + rmAddr, err := toQuicMultiaddr(addr) if err != nil { panic(err) } - if !c.filters.AddrBlocked(maddr) { + + connAddrs := &connAddrs{lmAddr: c.lmAddr, rmAddr: rmAddr} + + if c.gater.InterceptAccept(connAddrs) { return } } diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 9c77d24bac..1574ea2901 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -3,11 +3,14 @@ package libp2pquic import ( "context" "crypto/tls" + "fmt" "net" ic "github.com/libp2p/go-libp2p-core/crypto" + n "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" tpt "github.com/libp2p/go-libp2p-core/transport" + p2ptls "github.com/libp2p/go-libp2p-tls" quic "github.com/lucas-clemente/quic-go" @@ -79,6 +82,7 @@ func (l *listener) setupConn(sess quic.Session) (tpt.CapableConn, error) { if err != nil { return nil, err } + remotePeerID, err := peer.IDFromPublicKey(remotePubKey) if err != nil { return nil, err @@ -87,6 +91,12 @@ func (l *listener) setupConn(sess quic.Session) (tpt.CapableConn, error) { if err != nil { return nil, err } + + connaddrs := &connAddrs{lmAddr: l.localMultiaddr, rmAddr: remoteMultiaddr} + if l.transport.gater != nil && !l.transport.gater.InterceptSecured(n.DirInbound, remotePeerID, connaddrs) { + return nil, fmt.Errorf("secured connection gated") + } + return &conn{ sess: sess, transport: l.transport, diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go index f566de2fd0..d7f7f76127 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse.go @@ -5,7 +5,8 @@ import ( "sync" "time" - filter "github.com/libp2p/go-maddr-filter" + "github.com/libp2p/go-libp2p-core/connmgr" + "github.com/libp2p/go-netroute" ) @@ -23,9 +24,9 @@ type reuseConn struct { unusedSince time.Time } -func newReuseConn(conn net.PacketConn, filters *filter.Filters) *reuseConn { - if filters != nil { - conn = newFilteredConn(conn, filters) +func newReuseConn(conn net.PacketConn, gater connmgr.ConnectionGater) *reuseConn { + if gater != nil { + conn = newFilteredConn(conn, gater) } return &reuseConn{PacketConn: conn} } @@ -55,7 +56,7 @@ func (c *reuseConn) ShouldGarbageCollect(now time.Time) bool { type reuse struct { mutex sync.Mutex - filters *filter.Filters + gater connmgr.ConnectionGater garbageCollectorRunning bool @@ -64,9 +65,9 @@ type reuse struct { global map[int]*reuseConn } -func newReuse(filters *filter.Filters) *reuse { +func newReuse(gater connmgr.ConnectionGater) *reuse { return &reuse{ - filters: filters, + gater: gater, unicast: make(map[string]map[int]*reuseConn), global: make(map[int]*reuseConn), } @@ -168,7 +169,7 @@ func (r *reuse) dialLocked(network string, raddr *net.UDPAddr, source *net.IP) ( if err != nil { return nil, err } - rconn := newReuseConn(conn, r.filters) + rconn := newReuseConn(conn, r.gater) r.global[conn.LocalAddr().(*net.UDPAddr).Port] = rconn return rconn, nil } @@ -180,7 +181,7 @@ func (r *reuse) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { } localAddr := conn.LocalAddr().(*net.UDPAddr) - rconn := newReuseConn(conn, r.filters) + rconn := newReuseConn(conn, r.gater) rconn.IncreaseCount() r.mutex.Lock() diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index d71b366d7f..c7902b3614 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -3,9 +3,13 @@ package libp2pquic import ( "context" "errors" + "fmt" "io" "net" + "github.com/libp2p/go-libp2p-core/connmgr" + n "github.com/libp2p/go-libp2p-core/network" + "github.com/minio/sha256-simd" "golang.org/x/crypto/hkdf" @@ -15,7 +19,6 @@ import ( "github.com/libp2p/go-libp2p-core/pnet" tpt "github.com/libp2p/go-libp2p-core/transport" p2ptls "github.com/libp2p/go-libp2p-tls" - filter "github.com/libp2p/go-maddr-filter" quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" mafmt "github.com/multiformats/go-multiaddr-fmt" @@ -43,9 +46,9 @@ type connManager struct { reuseUDP6 *reuse } -func newConnManager(filters *filter.Filters) (*connManager, error) { - reuseUDP4 := newReuse(filters) - reuseUDP6 := newReuse(filters) +func newConnManager(gater connmgr.ConnectionGater) (*connManager, error) { + reuseUDP4 := newReuse(gater) + reuseUDP6 := newReuse(gater) return &connManager{ reuseUDP4: reuseUDP4, @@ -88,12 +91,13 @@ type transport struct { connManager *connManager serverConfig *quic.Config clientConfig *quic.Config + gater connmgr.ConnectionGater } var _ tpt.Transport = &transport{} // NewTransport creates a new QUIC transport -func NewTransport(key ic.PrivKey, psk pnet.PSK, filters *filter.Filters) (tpt.Transport, error) { +func NewTransport(key ic.PrivKey, psk pnet.PSK, gater connmgr.ConnectionGater) (tpt.Transport, error) { if len(psk) > 0 { log.Error("QUIC doesn't support private networks yet.") return nil, errors.New("QUIC doesn't support private networks yet") @@ -106,7 +110,7 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, filters *filter.Filters) (tpt.Tr if err != nil { return nil, err } - connManager, err := newConnManager(filters) + connManager, err := newConnManager(gater) if err != nil { return nil, err } @@ -128,6 +132,7 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, filters *filter.Filters) (tpt.Tr connManager: connManager, serverConfig: config, clientConfig: config.Clone(), + gater: gater, } t.serverConfig.GetLogWriter = getLogWriterFor("server") t.clientConfig.GetLogWriter = getLogWriterFor("client") @@ -178,6 +183,13 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp pconn.DecreaseCount() return nil, err } + + connaddrs := &connAddrs{lmAddr: localMultiaddr, rmAddr: remoteMultiaddr} + if t.gater != nil && !t.gater.InterceptSecured(n.DirOutbound, p, connaddrs) { + pconn.DecreaseCount() + return nil, fmt.Errorf("secured connection gated") + } + return &conn{ sess: sess, transport: t, From 9f966ee16c2d2e15be1c47f23d425a2f32c5ca19 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 21 May 2020 18:14:50 +0700 Subject: [PATCH 084/138] close the connection when it is refused by InterceptSecured --- p2p/transport/quic/conn_test.go | 2 +- p2p/transport/quic/transport.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 7847c28f2f..881bede8f1 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -49,7 +49,7 @@ func (c *mockGater) InterceptAddrDial(peer.ID, ma.Multiaddr) (allow bool) { func (c *mockGater) InterceptSecured(_ network.Direction, p peer.ID, _ network.ConnMultiaddrs) (allow bool) { c.lk.Lock() defer c.lk.Unlock() - return !(p == c.blockedPeer) + return p != c.blockedPeer } func (c *mockGater) InterceptUpgraded(network.Conn) (allow bool, reason control.DisconnectReason) { diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index c7902b3614..de3db787fe 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -180,13 +180,13 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp localMultiaddr, err := toQuicMultiaddr(pconn.LocalAddr()) if err != nil { - pconn.DecreaseCount() + sess.CloseWithError(0, "") return nil, err } connaddrs := &connAddrs{lmAddr: localMultiaddr, rmAddr: remoteMultiaddr} if t.gater != nil && !t.gater.InterceptSecured(n.DirOutbound, p, connaddrs) { - pconn.DecreaseCount() + sess.CloseWithError(0, "") return nil, fmt.Errorf("secured connection gated") } From 029459e4c9ad64660143141e7607b55ebc214e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 22 May 2020 10:33:08 +0100 Subject: [PATCH 085/138] fix a potential nil-pointer panic. (#158) --- p2p/transport/quic/filtered_conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/quic/filtered_conn.go b/p2p/transport/quic/filtered_conn.go index c5f046edab..d04abc50b5 100644 --- a/p2p/transport/quic/filtered_conn.go +++ b/p2p/transport/quic/filtered_conn.go @@ -51,7 +51,7 @@ func (c *filteredConn) ReadFrom(b []byte) (n int, addr net.Addr, rerr error) { connAddrs := &connAddrs{lmAddr: c.lmAddr, rmAddr: rmAddr} - if c.gater.InterceptAccept(connAddrs) { + if c.gater != nil && c.gater.InterceptAccept(connAddrs) { return } } From 094d042339cd86a012484ff997dfad209489ee71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 22 May 2020 11:35:59 +0100 Subject: [PATCH 086/138] Revert "fix a potential nil-pointer panic." (#159) --- p2p/transport/quic/filtered_conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/quic/filtered_conn.go b/p2p/transport/quic/filtered_conn.go index d04abc50b5..c5f046edab 100644 --- a/p2p/transport/quic/filtered_conn.go +++ b/p2p/transport/quic/filtered_conn.go @@ -51,7 +51,7 @@ func (c *filteredConn) ReadFrom(b []byte) (n int, addr net.Addr, rerr error) { connAddrs := &connAddrs{lmAddr: c.lmAddr, rmAddr: rmAddr} - if c.gater != nil && c.gater.InterceptAccept(connAddrs) { + if c.gater.InterceptAccept(connAddrs) { return } } From f56ac0c0b0f37b2b756304d79ca8a92b9850b563 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 4 Jun 2020 16:53:20 +0700 Subject: [PATCH 087/138] update quic-go to v0.16.1 --- p2p/transport/quic/conn_test.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 881bede8f1..8c74837aed 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -338,7 +338,18 @@ var _ = Describe("Connection", func() { Expect(err).ToNot(HaveOccurred()) conn, err := clientTransport.Dial(context.Background(), proxyAddr, serverID) Expect(err).ToNot(HaveOccurred()) - str, err := conn.OpenStream() + go func() { + defer GinkgoRecover() + conn, err := ln.Accept() + Expect(err).ToNot(HaveOccurred()) + str, err := conn.OpenStream() + Expect(err).ToNot(HaveOccurred()) + str.Write([]byte("foobar")) + }() + + str, err := conn.AcceptStream() + Expect(err).ToNot(HaveOccurred()) + _, err = str.Read(make([]byte, 6)) Expect(err).ToNot(HaveOccurred()) // Stop forwarding packets and close the server. @@ -351,9 +362,10 @@ var _ = Describe("Connection", func() { // Now that the new server is up, re-enable packet forwarding. atomic.StoreUint32(&drop, 0) + // Trigger something (not too small) to be sent, so that we receive the stateless reset. // The new server doesn't have any state for the previously established connection. // We expect it to send a stateless reset. - _, rerr := str.Write([]byte("foobar")) + _, rerr := str.Write([]byte("Lorem ipsum dolor sit amet.")) if rerr == nil { _, rerr = str.Read([]byte{0, 0}) } From ec4007ae1f718f424784b08395e03b4c6e776b24 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 20 Aug 2020 23:07:31 +0700 Subject: [PATCH 088/138] update quic-go to v0.18.0 (#171) --- p2p/transport/quic/qlog.go | 32 ++++++++++++++++++++------------ p2p/transport/quic/qlog_test.go | 20 +++++++++++--------- p2p/transport/quic/transport.go | 8 +++----- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/p2p/transport/quic/qlog.go b/p2p/transport/quic/qlog.go index 73c9a4e6e4..ffec5ac7cc 100644 --- a/p2p/transport/quic/qlog.go +++ b/p2p/transport/quic/qlog.go @@ -7,26 +7,30 @@ import ( "io" "os" "time" + + "github.com/lucas-clemente/quic-go/logging" + "github.com/lucas-clemente/quic-go/qlog" ) -var qlogDir string +var tracer logging.Tracer func init() { - qlogDir = os.Getenv("QLOGDIR") -} - -func getLogWriterFor(role string) func([]byte) io.WriteCloser { + qlogDir := os.Getenv("QLOGDIR") if len(qlogDir) == 0 { - return nil + return } - return func(connID []byte) io.WriteCloser { + initQlogger(qlogDir) +} + +func initQlogger(qlogDir string) { + tracer = qlog.NewTracer(func(role logging.Perspective, connID []byte) io.WriteCloser { // create the QLOGDIR, if it doesn't exist if err := os.MkdirAll(qlogDir, 0777); err != nil { log.Errorf("creating the QLOGDIR failed: %s", err) return nil } - return newQlogger(role, connID) - } + return newQlogger(qlogDir, role, connID) + }) } type qlogger struct { @@ -35,10 +39,14 @@ type qlogger struct { io.WriteCloser } -func newQlogger(role string, connID []byte) io.WriteCloser { +func newQlogger(qlogDir string, role logging.Perspective, connID []byte) io.WriteCloser { t := time.Now().UTC().Format("2006-01-02T15-04-05.999999999UTC") - finalFilename := fmt.Sprintf("%s%clog_%s_%s_%x.qlog.gz", qlogDir, os.PathSeparator, t, role, connID) - filename := fmt.Sprintf("%s%c.log_%s_%s_%x.qlog.gz.swp", qlogDir, os.PathSeparator, t, role, connID) + r := "server" + if role == logging.PerspectiveClient { + r = "client" + } + finalFilename := fmt.Sprintf("%s%clog_%s_%s_%x.qlog.gz", qlogDir, os.PathSeparator, t, r, connID) + filename := fmt.Sprintf("%s%c.log_%s_%s_%x.qlog.gz.swp", qlogDir, os.PathSeparator, t, r, connID) f, err := os.Create(filename) if err != nil { log.Errorf("unable to create qlog file %s: %s", filename, err) diff --git a/p2p/transport/quic/qlog_test.go b/p2p/transport/quic/qlog_test.go index dc28d50bd8..753a6d33f7 100644 --- a/p2p/transport/quic/qlog_test.go +++ b/p2p/transport/quic/qlog_test.go @@ -7,6 +7,8 @@ import ( "io/ioutil" "os" + "github.com/lucas-clemente/quic-go/logging" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -16,18 +18,18 @@ type nopCloser struct{} func (nopCloser) Close() error { return nil } var _ = Describe("qlogger", func() { - var origQlogDir string + var qlogDir string BeforeEach(func() { - origQlogDir = qlogDir - d, err := ioutil.TempDir("", "libp2p-quic-transport-test") + var err error + qlogDir, err = ioutil.TempDir("", "libp2p-quic-transport-test") Expect(err).ToNot(HaveOccurred()) - fmt.Fprintf(GinkgoWriter, "Creating temporary directory: %s\n", d) - qlogDir = d + fmt.Fprintf(GinkgoWriter, "Creating temporary directory: %s\n", qlogDir) + initQlogger(qlogDir) }) AfterEach(func() { - qlogDir = origQlogDir + Expect(os.RemoveAll(qlogDir)).To(Succeed()) }) getFile := func() os.FileInfo { @@ -38,7 +40,7 @@ var _ = Describe("qlogger", func() { } It("saves a qlog", func() { - logger := newQlogger("server", []byte{0xde, 0xad, 0xbe, 0xef}) + logger := newQlogger(qlogDir, logging.PerspectiveServer, []byte{0xde, 0xad, 0xbe, 0xef}) file := getFile() Expect(string(file.Name()[0])).To(Equal(".")) Expect(file.Name()).To(HaveSuffix(".qlog.gz.swp")) @@ -54,7 +56,7 @@ var _ = Describe("qlogger", func() { }) It("buffers", func() { - logger := newQlogger("server", []byte("connid")) + logger := newQlogger(qlogDir, logging.PerspectiveServer, []byte("connid")) initialSize := getFile().Size() // Do a small write. // Since the writter is buffered, this should not be written to disk yet. @@ -68,7 +70,7 @@ var _ = Describe("qlogger", func() { }) It("compresses", func() { - logger := newQlogger("server", []byte("connid")) + logger := newQlogger(qlogDir, logging.PerspectiveServer, []byte("connid")) logger.Write([]byte("foobar")) Expect(logger.Close()).To(Succeed()) compressed, err := ioutil.ReadFile(qlogDir + "/" + getFile().Name()) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index de3db787fe..17b8e60590 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -124,8 +124,9 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, gater connmgr.ConnectionGater) ( if _, err := io.ReadFull(keyReader, config.StatelessResetKey); err != nil { return nil, err } + config.Tracer = tracer - t := &transport{ + return &transport{ privKey: key, localPeer: localPeer, identity: identity, @@ -133,10 +134,7 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, gater connmgr.ConnectionGater) ( serverConfig: config, clientConfig: config.Clone(), gater: gater, - } - t.serverConfig.GetLogWriter = getLogWriterFor("server") - t.clientConfig.GetLogWriter = getLogWriterFor("client") - return t, nil + }, nil } // Dial dials a new QUIC connection From b5e31050575d4af68ac4206664c2ea51afdacbab Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 23 Jul 2020 12:16:05 +0700 Subject: [PATCH 089/138] enable metrics --- p2p/transport/quic/{qlog.go => tracer.go} | 15 +++++++++------ .../quic/{qlog_test.go => tracer_test.go} | 0 2 files changed, 9 insertions(+), 6 deletions(-) rename p2p/transport/quic/{qlog.go => tracer.go} (80%) rename p2p/transport/quic/{qlog_test.go => tracer_test.go} (100%) diff --git a/p2p/transport/quic/qlog.go b/p2p/transport/quic/tracer.go similarity index 80% rename from p2p/transport/quic/qlog.go rename to p2p/transport/quic/tracer.go index ffec5ac7cc..f88cbcee8c 100644 --- a/p2p/transport/quic/qlog.go +++ b/p2p/transport/quic/tracer.go @@ -9,21 +9,24 @@ import ( "time" "github.com/lucas-clemente/quic-go/logging" + "github.com/lucas-clemente/quic-go/metrics" "github.com/lucas-clemente/quic-go/qlog" ) var tracer logging.Tracer func init() { - qlogDir := os.Getenv("QLOGDIR") - if len(qlogDir) == 0 { - return + tracers := []logging.Tracer{metrics.NewTracer()} + if qlogDir := os.Getenv("QLOGDIR"); len(qlogDir) > 0 { + if qlogger := initQlogger(qlogDir); qlogger != nil { + tracers = append(tracers, qlogger) + } } - initQlogger(qlogDir) + tracer = logging.NewMultiplexedTracer(tracers...) } -func initQlogger(qlogDir string) { - tracer = qlog.NewTracer(func(role logging.Perspective, connID []byte) io.WriteCloser { +func initQlogger(qlogDir string) logging.Tracer { + return qlog.NewTracer(func(role logging.Perspective, connID []byte) io.WriteCloser { // create the QLOGDIR, if it doesn't exist if err := os.MkdirAll(qlogDir, 0777); err != nil { log.Errorf("creating the QLOGDIR failed: %s", err) diff --git a/p2p/transport/quic/qlog_test.go b/p2p/transport/quic/tracer_test.go similarity index 100% rename from p2p/transport/quic/qlog_test.go rename to p2p/transport/quic/tracer_test.go From 0b57bca69f5019ea03b465afc2e6c228a2794da8 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 27 Aug 2020 17:31:35 -0700 Subject: [PATCH 090/138] Implement CloseRead/CloseWrite This: * Changes `Close` to behave like the unix `close` (sends an EOF, ignores future inbound data). * Adds a `CloseWrite` to replace the current `Close`. * Adds a `CloseRead` to close the read side, while leaving the write side open. See https://github.com/libp2p/go-libp2p-core/pull/166. --- p2p/transport/quic/conn_test.go | 4 ++-- p2p/transport/quic/quic_multiaddr.go | 2 +- p2p/transport/quic/stream.go | 14 ++++++++++++++ p2p/transport/quic/transport.go | 2 +- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 8c74837aed..67d48e79a1 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -20,7 +20,7 @@ import ( quicproxy "github.com/lucas-clemente/quic-go/integrationtests/tools/proxy" ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr-net" + manet "github.com/multiformats/go-multiaddr/net" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -303,7 +303,7 @@ var _ = Describe("Connection", func() { defer GinkgoRecover() str, err := conn.AcceptStream() Expect(err).ToNot(HaveOccurred()) - str.Close() + str.CloseWrite() d, err := ioutil.ReadAll(str) Expect(err).ToNot(HaveOccurred()) Expect(d).To(Equal(data)) diff --git a/p2p/transport/quic/quic_multiaddr.go b/p2p/transport/quic/quic_multiaddr.go index 8b182b76ba..81b66af8aa 100644 --- a/p2p/transport/quic/quic_multiaddr.go +++ b/p2p/transport/quic/quic_multiaddr.go @@ -4,7 +4,7 @@ import ( "net" ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr-net" + manet "github.com/multiformats/go-multiaddr/net" ) var quicMA ma.Multiaddr diff --git a/p2p/transport/quic/stream.go b/p2p/transport/quic/stream.go index 313ea227ec..57ca2e9155 100644 --- a/p2p/transport/quic/stream.go +++ b/p2p/transport/quic/stream.go @@ -38,4 +38,18 @@ func (s *stream) Reset() error { return nil } +func (s *stream) Close() error { + s.Stream.CancelRead(reset) + return s.Stream.Close() +} + +func (s *stream) CloseRead() error { + s.Stream.CancelRead(reset) + return nil +} + +func (s *stream) CloseWrite() error { + return s.Stream.Close() +} + var _ mux.MuxedStream = &stream{} diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 17b8e60590..25d0e8a088 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -22,7 +22,7 @@ import ( quic "github.com/lucas-clemente/quic-go" 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" ) var log = logging.Logger("quic-transport") From 979147aed646dd290c996c3ff6165c2e81c31eae Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 5 Oct 2020 19:18:44 +0700 Subject: [PATCH 091/138] always close the connection in the cmd client --- p2p/transport/quic/cmd/client/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/p2p/transport/quic/cmd/client/main.go b/p2p/transport/quic/cmd/client/main.go index 37ede0c338..b17f47b33d 100644 --- a/p2p/transport/quic/cmd/client/main.go +++ b/p2p/transport/quic/cmd/client/main.go @@ -48,6 +48,7 @@ func run(raddr string, p string) error { if err != nil { return err } + defer conn.Close() str, err := conn.OpenStream() if err != nil { return err @@ -65,5 +66,5 @@ func run(raddr string, p string) error { return err } log.Printf("Received: %s\n", data) - return conn.Close() + return nil } From 3b88f7bbcc247fe74fccc75145c25fb417c30a2d Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 24 Oct 2020 12:44:34 +0700 Subject: [PATCH 092/138] use a dedicated error code when a connection is gated 0x47415445 is GATE in ASCII. --- p2p/transport/quic/listener.go | 12 +++++------- p2p/transport/quic/transport.go | 3 ++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 1574ea2901..8d738960a3 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -3,7 +3,6 @@ package libp2pquic import ( "context" "crypto/tls" - "fmt" "net" ic "github.com/libp2p/go-libp2p-core/crypto" @@ -69,11 +68,15 @@ func (l *listener) Accept() (tpt.CapableConn, error) { sess.CloseWithError(0, err.Error()) continue } + if l.transport.gater != nil && !l.transport.gater.InterceptSecured(n.DirInbound, conn.remotePeerID, conn) { + sess.CloseWithError(errorCodeConnectionGating, "connection gated") + continue + } return conn, nil } } -func (l *listener) setupConn(sess quic.Session) (tpt.CapableConn, error) { +func (l *listener) setupConn(sess quic.Session) (*conn, error) { // The tls.Config used to establish this connection already verified the certificate chain. // Since we don't have any way of knowing which tls.Config was used though, // we have to re-determine the peer's identity here. @@ -92,11 +95,6 @@ func (l *listener) setupConn(sess quic.Session) (tpt.CapableConn, error) { return nil, err } - connaddrs := &connAddrs{lmAddr: l.localMultiaddr, rmAddr: remoteMultiaddr} - if l.transport.gater != nil && !l.transport.gater.InterceptSecured(n.DirInbound, remotePeerID, connaddrs) { - return nil, fmt.Errorf("secured connection gated") - } - return &conn{ sess: sess, transport: l.transport, diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 25d0e8a088..3691260bc6 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -40,6 +40,7 @@ var quicConfig = &quic.Config{ } const statelessResetKeyInfo = "libp2p quic stateless reset key" +const errorCodeConnectionGating = 0x47415445 // GATE in ASCII type connManager struct { reuseUDP4 *reuse @@ -184,7 +185,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp connaddrs := &connAddrs{lmAddr: localMultiaddr, rmAddr: remoteMultiaddr} if t.gater != nil && !t.gater.InterceptSecured(n.DirOutbound, p, connaddrs) { - sess.CloseWithError(0, "") + sess.CloseWithError(errorCodeConnectionGating, "connection gated") return nil, fmt.Errorf("secured connection gated") } From 73961f0e83eab778f27f94c67c08b240c9b124b1 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 24 Oct 2020 14:55:27 +0700 Subject: [PATCH 093/138] use GoMock to generate a mock connection gater --- p2p/transport/quic/conn_test.go | 60 +++------- p2p/transport/quic/libp2pquic_suite_test.go | 6 + .../quic/mock_connection_gater_test.go | 109 ++++++++++++++++++ 3 files changed, 129 insertions(+), 46 deletions(-) create mode 100644 p2p/transport/quic/mock_connection_gater_test.go diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 67d48e79a1..f75504bc5a 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -8,54 +8,23 @@ import ( "io/ioutil" mrand "math/rand" "net" - "sync" "sync/atomic" "time" - "github.com/libp2p/go-libp2p-core/control" + gomock "github.com/golang/mock/gomock" ic "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/network" + n "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" tpt "github.com/libp2p/go-libp2p-core/transport" quicproxy "github.com/lucas-clemente/quic-go/integrationtests/tools/proxy" ma "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr/net" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) -type mockGater struct { - lk sync.Mutex - acceptAll bool - blockedPeer peer.ID -} - -func (c *mockGater) InterceptAccept(addrs network.ConnMultiaddrs) bool { - c.lk.Lock() - defer c.lk.Unlock() - return c.acceptAll || !manet.IsIPLoopback(addrs.RemoteMultiaddr()) -} - -func (c *mockGater) InterceptPeerDial(p peer.ID) (allow bool) { - return true -} - -func (c *mockGater) InterceptAddrDial(peer.ID, ma.Multiaddr) (allow bool) { - return true -} - -func (c *mockGater) InterceptSecured(_ network.Direction, p peer.ID, _ network.ConnMultiaddrs) (allow bool) { - c.lk.Lock() - defer c.lk.Unlock() - return p != c.blockedPeer -} - -func (c *mockGater) InterceptUpgraded(network.Conn) (allow bool, reason control.DisconnectReason) { - return true, 0 -} - +//go:generate sh -c "mockgen -package libp2pquic -destination mock_connection_gater_test.go github.com/libp2p/go-libp2p-core/connmgr ConnectionGater && goimports -w mock_connection_gater_test.go" var _ = Describe("Connection", func() { var ( serverKey, clientKey ic.PrivKey @@ -200,11 +169,11 @@ var _ = Describe("Connection", func() { }) It("gates accepted connections", func() { - testMA, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234/quic") - Expect(err).ToNot(HaveOccurred()) - cg := &mockGater{} - Expect(cg.InterceptAccept(&connAddrs{rmAddr: testMA})).To(BeFalse()) - + cg := NewMockConnectionGater(mockCtrl) + var allow bool + cg.EXPECT().InterceptAccept(gomock.Any()).DoAndReturn(func(n.ConnMultiaddrs) bool { + return allow + }).AnyTimes() serverTransport, err := NewTransport(serverKey, nil, cg) Expect(err).ToNot(HaveOccurred()) ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") @@ -220,10 +189,8 @@ var _ = Describe("Connection", func() { Expect(err.(net.Error).Timeout()).To(BeTrue()) // now allow the address and make sure the connection goes through + allow = true clientTransport.(*transport).clientConfig.HandshakeTimeout = 2 * time.Second - cg.lk.Lock() - cg.acceptAll = true - cg.lk.Unlock() conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) conn.Close() @@ -235,7 +202,10 @@ var _ = Describe("Connection", func() { ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() - cg := &mockGater{acceptAll: true, blockedPeer: serverID} + cg := NewMockConnectionGater(mockCtrl) + cg.EXPECT().InterceptAccept(gomock.Any()).Return(true).AnyTimes() + cg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()) + clientTransport, err := NewTransport(clientKey, nil, cg) Expect(err).ToNot(HaveOccurred()) @@ -245,10 +215,8 @@ var _ = Describe("Connection", func() { Expect(err).To(HaveOccurred()) // now allow the peerId and make sure the connection goes through + cg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true) clientTransport.(*transport).clientConfig.HandshakeTimeout = 2 * time.Second - cg.lk.Lock() - cg.blockedPeer = "none" - cg.lk.Unlock() conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) conn.Close() diff --git a/p2p/transport/quic/libp2pquic_suite_test.go b/p2p/transport/quic/libp2pquic_suite_test.go index 5905763c07..0415fed75a 100644 --- a/p2p/transport/quic/libp2pquic_suite_test.go +++ b/p2p/transport/quic/libp2pquic_suite_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + gomock "github.com/golang/mock/gomock" "github.com/lucas-clemente/quic-go" . "github.com/onsi/ginkgo" @@ -27,6 +28,7 @@ var ( garbageCollectIntervalOrig time.Duration maxUnusedDurationOrig time.Duration origQuicConfig *quic.Config + mockCtrl *gomock.Controller ) func isGarbageCollectorRunning() bool { @@ -36,6 +38,8 @@ func isGarbageCollectorRunning() bool { } var _ = BeforeEach(func() { + mockCtrl = gomock.NewController(GinkgoT()) + Expect(isGarbageCollectorRunning()).To(BeFalse()) garbageCollectIntervalOrig = garbageCollectInterval maxUnusedDurationOrig = maxUnusedDuration @@ -46,6 +50,8 @@ var _ = BeforeEach(func() { }) var _ = AfterEach(func() { + mockCtrl.Finish() + Eventually(isGarbageCollectorRunning).Should(BeFalse()) garbageCollectInterval = garbageCollectIntervalOrig maxUnusedDuration = maxUnusedDurationOrig diff --git a/p2p/transport/quic/mock_connection_gater_test.go b/p2p/transport/quic/mock_connection_gater_test.go new file mode 100644 index 0000000000..899a0c6d51 --- /dev/null +++ b/p2p/transport/quic/mock_connection_gater_test.go @@ -0,0 +1,109 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/libp2p/go-libp2p-core/connmgr (interfaces: ConnectionGater) + +// Package libp2pquic is a generated GoMock package. +package libp2pquic + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + control "github.com/libp2p/go-libp2p-core/control" + network "github.com/libp2p/go-libp2p-core/network" + peer "github.com/libp2p/go-libp2p-core/peer" + multiaddr "github.com/multiformats/go-multiaddr" +) + +// MockConnectionGater is a mock of ConnectionGater interface +type MockConnectionGater struct { + ctrl *gomock.Controller + recorder *MockConnectionGaterMockRecorder +} + +// MockConnectionGaterMockRecorder is the mock recorder for MockConnectionGater +type MockConnectionGaterMockRecorder struct { + mock *MockConnectionGater +} + +// NewMockConnectionGater creates a new mock instance +func NewMockConnectionGater(ctrl *gomock.Controller) *MockConnectionGater { + mock := &MockConnectionGater{ctrl: ctrl} + mock.recorder = &MockConnectionGaterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockConnectionGater) EXPECT() *MockConnectionGaterMockRecorder { + return m.recorder +} + +// InterceptAccept mocks base method +func (m *MockConnectionGater) InterceptAccept(arg0 network.ConnMultiaddrs) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InterceptAccept", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// InterceptAccept indicates an expected call of InterceptAccept +func (mr *MockConnectionGaterMockRecorder) InterceptAccept(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InterceptAccept", reflect.TypeOf((*MockConnectionGater)(nil).InterceptAccept), arg0) +} + +// InterceptAddrDial mocks base method +func (m *MockConnectionGater) InterceptAddrDial(arg0 peer.ID, arg1 multiaddr.Multiaddr) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InterceptAddrDial", arg0, arg1) + ret0, _ := ret[0].(bool) + return ret0 +} + +// InterceptAddrDial indicates an expected call of InterceptAddrDial +func (mr *MockConnectionGaterMockRecorder) InterceptAddrDial(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InterceptAddrDial", reflect.TypeOf((*MockConnectionGater)(nil).InterceptAddrDial), arg0, arg1) +} + +// InterceptPeerDial mocks base method +func (m *MockConnectionGater) InterceptPeerDial(arg0 peer.ID) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InterceptPeerDial", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// InterceptPeerDial indicates an expected call of InterceptPeerDial +func (mr *MockConnectionGaterMockRecorder) InterceptPeerDial(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InterceptPeerDial", reflect.TypeOf((*MockConnectionGater)(nil).InterceptPeerDial), arg0) +} + +// InterceptSecured mocks base method +func (m *MockConnectionGater) InterceptSecured(arg0 network.Direction, arg1 peer.ID, arg2 network.ConnMultiaddrs) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InterceptSecured", arg0, arg1, arg2) + ret0, _ := ret[0].(bool) + return ret0 +} + +// InterceptSecured indicates an expected call of InterceptSecured +func (mr *MockConnectionGaterMockRecorder) InterceptSecured(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InterceptSecured", reflect.TypeOf((*MockConnectionGater)(nil).InterceptSecured), arg0, arg1, arg2) +} + +// InterceptUpgraded mocks base method +func (m *MockConnectionGater) InterceptUpgraded(arg0 network.Conn) (bool, control.DisconnectReason) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InterceptUpgraded", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(control.DisconnectReason) + return ret0, ret1 +} + +// InterceptUpgraded indicates an expected call of InterceptUpgraded +func (mr *MockConnectionGaterMockRecorder) InterceptUpgraded(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InterceptUpgraded", reflect.TypeOf((*MockConnectionGater)(nil).InterceptUpgraded), arg0) +} From f3091f346a9533762f4df95ecc8f7608bfa96a24 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 24 Oct 2020 15:39:31 +0700 Subject: [PATCH 094/138] don't blackhole packets when gating via InterceptAccept --- p2p/transport/quic/conn_test.go | 35 ++++++++++------- p2p/transport/quic/filtered_conn.go | 58 ----------------------------- p2p/transport/quic/listener.go | 2 +- p2p/transport/quic/reuse.go | 3 -- p2p/transport/quic/transport.go | 16 ++++---- 5 files changed, 29 insertions(+), 85 deletions(-) delete mode 100644 p2p/transport/quic/filtered_conn.go diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index f75504bc5a..ac2e8fade6 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -13,7 +13,6 @@ import ( gomock "github.com/golang/mock/gomock" ic "github.com/libp2p/go-libp2p-core/crypto" - n "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" tpt "github.com/libp2p/go-libp2p-core/transport" @@ -170,30 +169,37 @@ var _ = Describe("Connection", func() { It("gates accepted connections", func() { cg := NewMockConnectionGater(mockCtrl) - var allow bool - cg.EXPECT().InterceptAccept(gomock.Any()).DoAndReturn(func(n.ConnMultiaddrs) bool { - return allow - }).AnyTimes() + cg.EXPECT().InterceptAccept(gomock.Any()) serverTransport, err := NewTransport(serverKey, nil, cg) Expect(err).ToNot(HaveOccurred()) ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() + accepted := make(chan struct{}) + go func() { + defer GinkgoRecover() + defer close(accepted) + _, err := ln.Accept() + Expect(err).ToNot(HaveOccurred()) + }() + clientTransport, err := NewTransport(clientKey, nil, nil) Expect(err).ToNot(HaveOccurred()) - // make sure that connection attempts fails - clientTransport.(*transport).clientConfig.HandshakeTimeout = 250 * time.Millisecond - _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) + conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) + Expect(err).ToNot(HaveOccurred()) + _, err = conn.AcceptStream() Expect(err).To(HaveOccurred()) - Expect(err.(net.Error).Timeout()).To(BeTrue()) + Expect(err.Error()).To(ContainSubstring("connection gated")) // now allow the address and make sure the connection goes through - allow = true + cg.EXPECT().InterceptAccept(gomock.Any()).Return(true) + cg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true) clientTransport.(*transport).clientConfig.HandshakeTimeout = 2 * time.Second - conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) + conn, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) - conn.Close() + defer conn.Close() + Eventually(accepted).Should(BeClosed()) }) It("gates secured connections", func() { @@ -203,18 +209,19 @@ var _ = Describe("Connection", func() { defer ln.Close() cg := NewMockConnectionGater(mockCtrl) - cg.EXPECT().InterceptAccept(gomock.Any()).Return(true).AnyTimes() + cg.EXPECT().InterceptAccept(gomock.Any()).Return(true) cg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()) clientTransport, err := NewTransport(clientKey, nil, cg) Expect(err).ToNot(HaveOccurred()) // make sure that connection attempts fails - clientTransport.(*transport).clientConfig.HandshakeTimeout = 250 * time.Millisecond _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("connection gated")) // now allow the peerId and make sure the connection goes through + cg.EXPECT().InterceptAccept(gomock.Any()).Return(true) cg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true) clientTransport.(*transport).clientConfig.HandshakeTimeout = 2 * time.Second conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) diff --git a/p2p/transport/quic/filtered_conn.go b/p2p/transport/quic/filtered_conn.go deleted file mode 100644 index c5f046edab..0000000000 --- a/p2p/transport/quic/filtered_conn.go +++ /dev/null @@ -1,58 +0,0 @@ -package libp2pquic - -import ( - "net" - - "github.com/libp2p/go-libp2p-core/connmgr" - - ma "github.com/multiformats/go-multiaddr" -) - -type connAddrs struct { - lmAddr ma.Multiaddr - rmAddr ma.Multiaddr -} - -func (c *connAddrs) LocalMultiaddr() ma.Multiaddr { - return c.lmAddr -} - -func (c *connAddrs) RemoteMultiaddr() ma.Multiaddr { - return c.rmAddr -} - -type filteredConn struct { - net.PacketConn - - lmAddr ma.Multiaddr - gater connmgr.ConnectionGater -} - -func newFilteredConn(c net.PacketConn, gater connmgr.ConnectionGater) net.PacketConn { - lmAddr, err := toQuicMultiaddr(c.LocalAddr()) - if err != nil { - panic(err) - } - - return &filteredConn{PacketConn: c, gater: gater, lmAddr: lmAddr} -} - -func (c *filteredConn) ReadFrom(b []byte) (n int, addr net.Addr, rerr error) { - for { - n, addr, rerr = c.PacketConn.ReadFrom(b) - // Short Header packet, see https://tools.ietf.org/html/draft-ietf-quic-invariants-07#section-4.2. - if n < 1 || b[0]&0x80 == 0 { - return - } - rmAddr, err := toQuicMultiaddr(addr) - if err != nil { - panic(err) - } - - connAddrs := &connAddrs{lmAddr: c.lmAddr, rmAddr: rmAddr} - - if c.gater.InterceptAccept(connAddrs) { - return - } - } -} diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 8d738960a3..a44e4077cf 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -68,7 +68,7 @@ func (l *listener) Accept() (tpt.CapableConn, error) { sess.CloseWithError(0, err.Error()) continue } - if l.transport.gater != nil && !l.transport.gater.InterceptSecured(n.DirInbound, conn.remotePeerID, conn) { + if l.transport.gater != nil && !(l.transport.gater.InterceptAccept(conn) && l.transport.gater.InterceptSecured(n.DirInbound, conn.remotePeerID, conn)) { sess.CloseWithError(errorCodeConnectionGating, "connection gated") continue } diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go index d7f7f76127..e9582cd958 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse.go @@ -25,9 +25,6 @@ type reuseConn struct { } func newReuseConn(conn net.PacketConn, gater connmgr.ConnectionGater) *reuseConn { - if gater != nil { - conn = newFilteredConn(conn, gater) - } return &reuseConn{PacketConn: conn} } diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 3691260bc6..8916fbb531 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -182,14 +182,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp sess.CloseWithError(0, "") return nil, err } - - connaddrs := &connAddrs{lmAddr: localMultiaddr, rmAddr: remoteMultiaddr} - if t.gater != nil && !t.gater.InterceptSecured(n.DirOutbound, p, connaddrs) { - sess.CloseWithError(errorCodeConnectionGating, "connection gated") - return nil, fmt.Errorf("secured connection gated") - } - - return &conn{ + conn := &conn{ sess: sess, transport: t, privKey: t.privKey, @@ -198,7 +191,12 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp remotePubKey: remotePubKey, remotePeerID: p, remoteMultiaddr: remoteMultiaddr, - }, nil + } + if t.gater != nil && !(t.gater.InterceptAccept(conn) && t.gater.InterceptSecured(n.DirOutbound, p, conn)) { + sess.CloseWithError(errorCodeConnectionGating, "connection gated") + return nil, fmt.Errorf("secured connection gated") + } + return conn, nil } // Don't use mafmt.QUIC as we don't want to dial DNS addresses. Just /ip{4,6}/udp/quic From 81afd82a00b513b78d117f56085ee95de79e2058 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 24 Oct 2020 16:35:51 +0700 Subject: [PATCH 095/138] fix reuseConn counting when creating the quic.Listener errors --- p2p/transport/quic/transport.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 8916fbb531..c0d6055afe 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -221,7 +221,12 @@ func (t *transport) Listen(addr ma.Multiaddr) (tpt.Listener, error) { if err != nil { return nil, err } - return newListener(conn, t, t.localPeer, t.privKey, t.identity) + ln, err := newListener(conn, t, t.localPeer, t.privKey, t.identity) + if err != nil { + conn.DecreaseCount() + return nil, err + } + return ln, nil } // Proxy returns true if this transport proxies. From 773d8f010885ea6a875de1228cc8e69c2d0a6849 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 24 Oct 2020 16:50:12 +0700 Subject: [PATCH 096/138] pass a conn that can be type asserted to a net.UDPConn to quic-go --- p2p/transport/quic/listener.go | 4 +++- p2p/transport/quic/listener_test.go | 30 +++++++++++++++++++++++ p2p/transport/quic/reuse.go | 6 ++--- p2p/transport/quic/transport.go | 4 +++- p2p/transport/quic/transport_test.go | 36 +++++++++++++++++++++++++++- 5 files changed, 74 insertions(+), 6 deletions(-) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index a44e4077cf..70fe471c4a 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -16,6 +16,8 @@ import ( ma "github.com/multiformats/go-multiaddr" ) +var quicListen = quic.Listen // so we can mock it in tests + // A listener listens for QUIC connections. type listener struct { quicListener quic.Listener @@ -38,7 +40,7 @@ func newListener(rconn *reuseConn, t *transport, localPeer peer.ID, key ic.PrivK conf, _ := identity.ConfigForAny() return conf, nil } - ln, err := quic.Listen(rconn, &tlsConf, t.serverConfig) + ln, err := quicListen(rconn, &tlsConf, t.serverConfig) if err != nil { return nil, err } diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index 3388f1ac37..cb5836ec35 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -3,18 +3,29 @@ package libp2pquic import ( "crypto/rand" "crypto/rsa" + "crypto/tls" "crypto/x509" + "errors" "fmt" "net" + "syscall" ic "github.com/libp2p/go-libp2p-core/crypto" tpt "github.com/libp2p/go-libp2p-core/transport" + quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) +// interface containing some methods defined on the net.UDPConn, but not the net.PacketConn +type udpConn interface { + ReadFromUDP(b []byte) (int, *net.UDPAddr, error) + SetReadBuffer(bytes int) error + SyscallConn() (syscall.RawConn, error) +} + var _ = Describe("Listener", func() { var t tpt.Transport @@ -27,6 +38,25 @@ var _ = Describe("Listener", func() { Expect(err).ToNot(HaveOccurred()) }) + It("uses a conn that can interface assert to a UDPConn for listening", func() { + origQuicListen := quicListen + defer func() { quicListen = origQuicListen }() + + var conn net.PacketConn + quicListen = func(c net.PacketConn, _ *tls.Config, _ *quic.Config) (quic.Listener, error) { + conn = c + return nil, errors.New("listen error") + } + localAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + Expect(err).ToNot(HaveOccurred()) + _, err = t.Listen(localAddr) + Expect(err).To(MatchError("listen error")) + Expect(conn).ToNot(BeNil()) + defer conn.Close() + _, ok := conn.(udpConn) + Expect(ok).To(BeTrue()) + }) + Context("listening on the right address", func() { It("returns the address it is listening on", func() { localAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go index e9582cd958..3aa6940298 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse.go @@ -17,15 +17,15 @@ var ( ) type reuseConn struct { - net.PacketConn + *net.UDPConn mutex sync.Mutex refCount int unusedSince time.Time } -func newReuseConn(conn net.PacketConn, gater connmgr.ConnectionGater) *reuseConn { - return &reuseConn{PacketConn: conn} +func newReuseConn(conn *net.UDPConn, gater connmgr.ConnectionGater) *reuseConn { + return &reuseConn{UDPConn: conn} } func (c *reuseConn) IncreaseCount() { diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index c0d6055afe..b28b8b0aaa 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -27,6 +27,8 @@ import ( var log = logging.Logger("quic-transport") +var quicDialContext = quic.DialContext // so we can mock it in tests + var quicConfig = &quic.Config{ MaxIncomingStreams: 1000, MaxIncomingUniStreams: -1, // disable unidirectional streams @@ -157,7 +159,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if err != nil { return nil, err } - sess, err := quic.DialContext(ctx, pconn, addr, host, tlsConf, t.clientConfig) + sess, err := quicDialContext(ctx, pconn, addr, host, tlsConf, t.clientConfig) if err != nil { pconn.DecreaseCount() return nil, err diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go index 111b702c8c..226ec2f9c5 100644 --- a/p2p/transport/quic/transport_test.go +++ b/p2p/transport/quic/transport_test.go @@ -1,7 +1,17 @@ package libp2pquic import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "errors" + "net" + + ic "github.com/libp2p/go-libp2p-core/crypto" tpt "github.com/libp2p/go-libp2p-core/transport" + quic "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" . "github.com/onsi/ginkgo" @@ -12,7 +22,12 @@ var _ = Describe("Transport", func() { var t tpt.Transport BeforeEach(func() { - t = &transport{} + rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) + Expect(err).ToNot(HaveOccurred()) + key, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(rsaKey)) + Expect(err).ToNot(HaveOccurred()) + t, err = NewTransport(key, nil, nil) + Expect(err).ToNot(HaveOccurred()) }) It("says if it can dial an address", func() { @@ -35,4 +50,23 @@ var _ = Describe("Transport", func() { Expect(protocols).To(HaveLen(1)) Expect(protocols[0]).To(Equal(ma.P_QUIC)) }) + + It("uses a conn that can interface assert to a UDPConn for dialing", func() { + origQuicDialContext := quicDialContext + defer func() { quicDialContext = origQuicDialContext }() + + var conn net.PacketConn + quicDialContext = func(_ context.Context, c net.PacketConn, _ net.Addr, _ string, _ *tls.Config, _ *quic.Config) (quic.Session, error) { + conn = c + return nil, errors.New("listen error") + } + remoteAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + Expect(err).ToNot(HaveOccurred()) + _, err = t.Dial(context.Background(), remoteAddr, "remote peer id") + Expect(err).To(MatchError("listen error")) + Expect(conn).ToNot(BeNil()) + defer conn.Close() + _, ok := conn.(udpConn) + Expect(ok).To(BeTrue()) + }) }) From f9838545caab7e381513ee7e55b6ea19e8800216 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 27 Oct 2020 08:52:12 +0700 Subject: [PATCH 097/138] don't call InterceptAccept when dialing --- p2p/transport/quic/conn_test.go | 2 -- p2p/transport/quic/transport.go | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index ac2e8fade6..172acff638 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -209,7 +209,6 @@ var _ = Describe("Connection", func() { defer ln.Close() cg := NewMockConnectionGater(mockCtrl) - cg.EXPECT().InterceptAccept(gomock.Any()).Return(true) cg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()) clientTransport, err := NewTransport(clientKey, nil, cg) @@ -221,7 +220,6 @@ var _ = Describe("Connection", func() { Expect(err.Error()).To(ContainSubstring("connection gated")) // now allow the peerId and make sure the connection goes through - cg.EXPECT().InterceptAccept(gomock.Any()).Return(true) cg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true) clientTransport.(*transport).clientConfig.HandshakeTimeout = 2 * time.Second conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index b28b8b0aaa..ea850f1114 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -194,7 +194,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp remotePeerID: p, remoteMultiaddr: remoteMultiaddr, } - if t.gater != nil && !(t.gater.InterceptAccept(conn) && t.gater.InterceptSecured(n.DirOutbound, p, conn)) { + if t.gater != nil && !t.gater.InterceptSecured(n.DirOutbound, p, conn) { sess.CloseWithError(errorCodeConnectionGating, "connection gated") return nil, fmt.Errorf("secured connection gated") } From 728d351233fc0601f4840ee789c87d895938f466 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 11 Nov 2020 18:01:54 +0700 Subject: [PATCH 098/138] update quic-go to v0.19.0 --- p2p/transport/quic/transport.go | 1 + 1 file changed, 1 insertion(+) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index ea850f1114..2be59bc2eb 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -39,6 +39,7 @@ var quicConfig = &quic.Config{ return true }, KeepAlive: true, + Versions: []quic.VersionNumber{quic.VersionDraft29, quic.VersionDraft32}, } const statelessResetKeyInfo = "libp2p quic stateless reset key" From ee958afaf3be74a7cdf27309d0dcf9b04bdf1dbd Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 17 Dec 2020 15:58:43 +0700 Subject: [PATCH 099/138] change OpenStream to accept a context --- p2p/transport/quic/cmd/client/main.go | 2 +- p2p/transport/quic/conn.go | 4 ++-- p2p/transport/quic/conn_test.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/p2p/transport/quic/cmd/client/main.go b/p2p/transport/quic/cmd/client/main.go index b17f47b33d..bc033e8718 100644 --- a/p2p/transport/quic/cmd/client/main.go +++ b/p2p/transport/quic/cmd/client/main.go @@ -49,7 +49,7 @@ func run(raddr string, p string) error { return err } defer conn.Close() - str, err := conn.OpenStream() + str, err := conn.OpenStream(context.Background()) if err != nil { return err } diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index 9ba8593b72..4f74d25c6e 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -37,8 +37,8 @@ func (c *conn) IsClosed() bool { } // OpenStream creates a new stream. -func (c *conn) OpenStream() (mux.MuxedStream, error) { - qstr, err := c.sess.OpenStreamSync(context.Background()) +func (c *conn) OpenStream(ctx context.Context) (mux.MuxedStream, error) { + qstr, err := c.sess.OpenStreamSync(ctx) return &stream{Stream: qstr}, err } diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 172acff638..0eee557405 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -130,7 +130,7 @@ var _ = Describe("Connection", func() { Expect(err).ToNot(HaveOccurred()) defer serverConn.Close() - str, err := conn.OpenStream() + str, err := conn.OpenStream(context.Background()) Expect(err).ToNot(HaveOccurred()) _, err = str.Write([]byte("foobar")) Expect(err).ToNot(HaveOccurred()) @@ -251,7 +251,7 @@ var _ = Describe("Connection", func() { for _, c := range []tpt.CapableConn{serverConn1, serverConn2} { go func(conn tpt.CapableConn) { defer GinkgoRecover() - str, err := conn.OpenStream() + str, err := conn.OpenStream(context.Background()) Expect(err).ToNot(HaveOccurred()) defer str.Close() _, err = str.Write(data) @@ -315,7 +315,7 @@ var _ = Describe("Connection", func() { defer GinkgoRecover() conn, err := ln.Accept() Expect(err).ToNot(HaveOccurred()) - str, err := conn.OpenStream() + str, err := conn.OpenStream(context.Background()) Expect(err).ToNot(HaveOccurred()) str.Write([]byte("foobar")) }() From 89dffadb02d7874f5ca1dc7a02bdbdb5f0e2ba80 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 19 Jan 2021 19:21:58 +0800 Subject: [PATCH 100/138] switch from gzip to zstd for qlog compression --- p2p/transport/quic/tracer.go | 13 +++++++++---- p2p/transport/quic/tracer_test.go | 9 +++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/p2p/transport/quic/tracer.go b/p2p/transport/quic/tracer.go index f88cbcee8c..f8e1dbb644 100644 --- a/p2p/transport/quic/tracer.go +++ b/p2p/transport/quic/tracer.go @@ -2,12 +2,13 @@ package libp2pquic import ( "bufio" - "compress/gzip" "fmt" "io" "os" "time" + "github.com/klauspost/compress/zstd" + "github.com/lucas-clemente/quic-go/logging" "github.com/lucas-clemente/quic-go/metrics" "github.com/lucas-clemente/quic-go/qlog" @@ -48,14 +49,18 @@ func newQlogger(qlogDir string, role logging.Perspective, connID []byte) io.Writ if role == logging.PerspectiveClient { r = "client" } - finalFilename := fmt.Sprintf("%s%clog_%s_%s_%x.qlog.gz", qlogDir, os.PathSeparator, t, r, connID) - filename := fmt.Sprintf("%s%c.log_%s_%s_%x.qlog.gz.swp", qlogDir, os.PathSeparator, t, r, connID) + finalFilename := fmt.Sprintf("%s%clog_%s_%s_%x.qlog.zst", qlogDir, os.PathSeparator, t, r, connID) + filename := fmt.Sprintf("%s%c.log_%s_%s_%x.qlog.zst.swp", qlogDir, os.PathSeparator, t, r, connID) f, err := os.Create(filename) if err != nil { log.Errorf("unable to create qlog file %s: %s", filename, err) return nil } - gz := gzip.NewWriter(f) + gz, err := zstd.NewWriter(f, zstd.WithEncoderLevel(zstd.SpeedFastest)) + if err != nil { + log.Errorf("failed to initialize zstd: %s", err) + return nil + } return &qlogger{ f: f, filename: finalFilename, diff --git a/p2p/transport/quic/tracer_test.go b/p2p/transport/quic/tracer_test.go index 753a6d33f7..5035f88f69 100644 --- a/p2p/transport/quic/tracer_test.go +++ b/p2p/transport/quic/tracer_test.go @@ -2,11 +2,12 @@ package libp2pquic import ( "bytes" - "compress/gzip" "fmt" "io/ioutil" "os" + "github.com/klauspost/compress/zstd" + "github.com/lucas-clemente/quic-go/logging" . "github.com/onsi/ginkgo" @@ -43,12 +44,12 @@ var _ = Describe("qlogger", func() { logger := newQlogger(qlogDir, logging.PerspectiveServer, []byte{0xde, 0xad, 0xbe, 0xef}) file := getFile() Expect(string(file.Name()[0])).To(Equal(".")) - Expect(file.Name()).To(HaveSuffix(".qlog.gz.swp")) + Expect(file.Name()).To(HaveSuffix(".qlog.zst.swp")) // close the logger. This should move the file. Expect(logger.Close()).To(Succeed()) file = getFile() Expect(string(file.Name()[0])).ToNot(Equal(".")) - Expect(file.Name()).To(HaveSuffix(".qlog.gz")) + Expect(file.Name()).To(HaveSuffix(".qlog.zst")) Expect(file.Name()).To(And( ContainSubstring("server"), ContainSubstring("deadbeef"), @@ -76,7 +77,7 @@ var _ = Describe("qlogger", func() { compressed, err := ioutil.ReadFile(qlogDir + "/" + getFile().Name()) Expect(err).ToNot(HaveOccurred()) Expect(compressed).ToNot(Equal("foobar")) - gz, err := gzip.NewReader(bytes.NewReader(compressed)) + gz, err := zstd.NewReader(bytes.NewReader(compressed)) Expect(err).ToNot(HaveOccurred()) data, err := ioutil.ReadAll(gz) Expect(err).ToNot(HaveOccurred()) From e888974d0e8e022164999c513a89183d5a16a022 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 11 Feb 2021 12:42:00 +0800 Subject: [PATCH 101/138] compress qlogs when the QUIC connection is closed --- p2p/transport/quic/tracer.go | 67 ++++++++++++++++--------------- p2p/transport/quic/tracer_test.go | 10 ++--- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/p2p/transport/quic/tracer.go b/p2p/transport/quic/tracer.go index f8e1dbb644..993c1a89e8 100644 --- a/p2p/transport/quic/tracer.go +++ b/p2p/transport/quic/tracer.go @@ -37,10 +37,14 @@ func initQlogger(qlogDir string) logging.Tracer { }) } +// The qlogger logs qlog events to a temporary file: ..qlog.swp. +// When it is closed, it compresses the temporary file and saves it as .qlog.zst. +// It is not possible to compress on the fly, as compression algorithms keep a lot of internal state, +// which can easily exhaust the host system's memory when running a few hundred QUIC connections in parallel. type qlogger struct { - f *os.File // QLOGDIR/.log_xxx.qlog.gz.swp - filename string // QLOGDIR/log_xxx.qlog.gz - io.WriteCloser + f *os.File // QLOGDIR/.log_xxx.qlog.swp + filename string // QLOGDIR/log_xxx.qlog.zst + *bufio.Writer // buffering the f } func newQlogger(qlogDir string, role logging.Perspective, connID []byte) io.WriteCloser { @@ -50,50 +54,49 @@ func newQlogger(qlogDir string, role logging.Perspective, connID []byte) io.Writ r = "client" } finalFilename := fmt.Sprintf("%s%clog_%s_%s_%x.qlog.zst", qlogDir, os.PathSeparator, t, r, connID) - filename := fmt.Sprintf("%s%c.log_%s_%s_%x.qlog.zst.swp", qlogDir, os.PathSeparator, t, r, connID) + filename := fmt.Sprintf("%s%c.log_%s_%s_%x.qlog.swp", qlogDir, os.PathSeparator, t, r, connID) f, err := os.Create(filename) if err != nil { log.Errorf("unable to create qlog file %s: %s", filename, err) return nil } - gz, err := zstd.NewWriter(f, zstd.WithEncoderLevel(zstd.SpeedFastest)) - if err != nil { - log.Errorf("failed to initialize zstd: %s", err) - return nil - } return &qlogger{ - f: f, - filename: finalFilename, - WriteCloser: newBufferedWriteCloser(bufio.NewWriter(gz), gz), + f: f, + filename: finalFilename, + Writer: bufio.NewWriter(f), } } func (l *qlogger) Close() error { - if err := l.WriteCloser.Close(); err != nil { + if err := l.Writer.Flush(); err != nil { return err } - path := l.f.Name() - if err := l.f.Close(); err != nil { + if _, err := l.f.Seek(0, io.SeekStart); err != nil { // set the read position to the beginning of the file return err } - return os.Rename(path, l.filename) -} - -type bufferedWriteCloser struct { - *bufio.Writer - io.Closer -} - -func newBufferedWriteCloser(writer *bufio.Writer, closer io.Closer) io.WriteCloser { - return &bufferedWriteCloser{ - Writer: writer, - Closer: closer, + f, err := os.Create(l.filename) + if err != nil { + return err } -} - -func (h bufferedWriteCloser) Close() error { - if err := h.Writer.Flush(); err != nil { + buf := bufio.NewWriter(f) + c, err := zstd.NewWriter(buf, zstd.WithEncoderLevel(zstd.SpeedFastest)) + if err != nil { + return err + } + if _, err := io.Copy(c, l.f); err != nil { + return err + } + if err := c.Close(); err != nil { + return err + } + if err := buf.Flush(); err != nil { + return err + } + if err := l.f.Close(); err != nil { + return err + } + if err := os.Remove(l.f.Name()); err != nil { return err } - return h.Closer.Close() + return f.Close() } diff --git a/p2p/transport/quic/tracer_test.go b/p2p/transport/quic/tracer_test.go index 5035f88f69..145a3261c6 100644 --- a/p2p/transport/quic/tracer_test.go +++ b/p2p/transport/quic/tracer_test.go @@ -14,10 +14,6 @@ import ( . "github.com/onsi/gomega" ) -type nopCloser struct{} - -func (nopCloser) Close() error { return nil } - var _ = Describe("qlogger", func() { var qlogDir string @@ -44,7 +40,7 @@ var _ = Describe("qlogger", func() { logger := newQlogger(qlogDir, logging.PerspectiveServer, []byte{0xde, 0xad, 0xbe, 0xef}) file := getFile() Expect(string(file.Name()[0])).To(Equal(".")) - Expect(file.Name()).To(HaveSuffix(".qlog.zst.swp")) + Expect(file.Name()).To(HaveSuffix(".qlog.swp")) // close the logger. This should move the file. Expect(logger.Close()).To(Succeed()) file = getFile() @@ -77,9 +73,9 @@ var _ = Describe("qlogger", func() { compressed, err := ioutil.ReadFile(qlogDir + "/" + getFile().Name()) Expect(err).ToNot(HaveOccurred()) Expect(compressed).ToNot(Equal("foobar")) - gz, err := zstd.NewReader(bytes.NewReader(compressed)) + c, err := zstd.NewReader(bytes.NewReader(compressed)) Expect(err).ToNot(HaveOccurred()) - data, err := ioutil.ReadAll(gz) + data, err := ioutil.ReadAll(c) Expect(err).ToNot(HaveOccurred()) Expect(data).To(Equal([]byte("foobar"))) }) From 593727b654868612e7bb87438863e8c7d2716a98 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 12 Feb 2021 10:41:06 +0800 Subject: [PATCH 102/138] use defers to close files --- p2p/transport/quic/tracer.go | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/p2p/transport/quic/tracer.go b/p2p/transport/quic/tracer.go index 993c1a89e8..a316772540 100644 --- a/p2p/transport/quic/tracer.go +++ b/p2p/transport/quic/tracer.go @@ -68,6 +68,8 @@ func newQlogger(qlogDir string, role logging.Perspective, connID []byte) io.Writ } func (l *qlogger) Close() error { + defer os.Remove(l.f.Name()) + defer l.f.Close() if err := l.Writer.Flush(); err != nil { return err } @@ -78,6 +80,7 @@ func (l *qlogger) Close() error { if err != nil { return err } + defer f.Close() buf := bufio.NewWriter(f) c, err := zstd.NewWriter(buf, zstd.WithEncoderLevel(zstd.SpeedFastest)) if err != nil { @@ -89,14 +92,5 @@ func (l *qlogger) Close() error { if err := c.Close(); err != nil { return err } - if err := buf.Flush(); err != nil { - return err - } - if err := l.f.Close(); err != nil { - return err - } - if err := os.Remove(l.f.Name()); err != nil { - return err - } - return f.Close() + return buf.Flush() } From 6f266985863acd4ff6a81e72e9833fd48007dffa Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 22 Feb 2021 12:38:41 +0800 Subject: [PATCH 103/138] reduce the zstd window size from 8 MB to 32 KB Benchmarks using sample qlog files show that this achieves in improvement in both compression efficiency and compression speed. More importantly, it prevents us from allocating a 8 MB every time a QUIC connection is closed. --- p2p/transport/quic/tracer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/quic/tracer.go b/p2p/transport/quic/tracer.go index a316772540..d46a4a9867 100644 --- a/p2p/transport/quic/tracer.go +++ b/p2p/transport/quic/tracer.go @@ -82,7 +82,7 @@ func (l *qlogger) Close() error { } defer f.Close() buf := bufio.NewWriter(f) - c, err := zstd.NewWriter(buf, zstd.WithEncoderLevel(zstd.SpeedFastest)) + c, err := zstd.NewWriter(buf, zstd.WithEncoderLevel(zstd.SpeedFastest), zstd.WithWindowSize(32*1024)) if err != nil { return err } From 781356a858d5eddbdd1c55f5a2b3e026f48b03e2 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 19 Mar 2021 12:24:20 +0800 Subject: [PATCH 104/138] update quic-go to v0.20.0 --- p2p/transport/quic/conn_test.go | 4 +-- p2p/transport/quic/listener.go | 2 +- .../quic/mock_connection_gater_test.go | 28 +++++++++---------- p2p/transport/quic/tracer.go | 3 +- p2p/transport/quic/transport.go | 8 +++--- 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 0eee557405..12e0647e65 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -195,7 +195,7 @@ var _ = Describe("Connection", func() { // now allow the address and make sure the connection goes through cg.EXPECT().InterceptAccept(gomock.Any()).Return(true) cg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true) - clientTransport.(*transport).clientConfig.HandshakeTimeout = 2 * time.Second + clientTransport.(*transport).clientConfig.HandshakeIdleTimeout = 2 * time.Second conn, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) defer conn.Close() @@ -221,7 +221,7 @@ var _ = Describe("Connection", func() { // now allow the peerId and make sure the connection goes through cg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true) - clientTransport.(*transport).clientConfig.HandshakeTimeout = 2 * time.Second + clientTransport.(*transport).clientConfig.HandshakeIdleTimeout = 2 * time.Second conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) conn.Close() diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 70fe471c4a..a61f312ed5 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -83,7 +83,7 @@ func (l *listener) setupConn(sess quic.Session) (*conn, error) { // Since we don't have any way of knowing which tls.Config was used though, // we have to re-determine the peer's identity here. // Therefore, this is expected to never fail. - remotePubKey, err := p2ptls.PubKeyFromCertChain(sess.ConnectionState().PeerCertificates) + remotePubKey, err := p2ptls.PubKeyFromCertChain(sess.ConnectionState().TLS.PeerCertificates) if err != nil { return nil, err } diff --git a/p2p/transport/quic/mock_connection_gater_test.go b/p2p/transport/quic/mock_connection_gater_test.go index 899a0c6d51..3a2cdcdb62 100644 --- a/p2p/transport/quic/mock_connection_gater_test.go +++ b/p2p/transport/quic/mock_connection_gater_test.go @@ -14,30 +14,30 @@ import ( multiaddr "github.com/multiformats/go-multiaddr" ) -// MockConnectionGater is a mock of ConnectionGater interface +// MockConnectionGater is a mock of ConnectionGater interface. type MockConnectionGater struct { ctrl *gomock.Controller recorder *MockConnectionGaterMockRecorder } -// MockConnectionGaterMockRecorder is the mock recorder for MockConnectionGater +// MockConnectionGaterMockRecorder is the mock recorder for MockConnectionGater. type MockConnectionGaterMockRecorder struct { mock *MockConnectionGater } -// NewMockConnectionGater creates a new mock instance +// NewMockConnectionGater creates a new mock instance. func NewMockConnectionGater(ctrl *gomock.Controller) *MockConnectionGater { mock := &MockConnectionGater{ctrl: ctrl} mock.recorder = &MockConnectionGaterMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockConnectionGater) EXPECT() *MockConnectionGaterMockRecorder { return m.recorder } -// InterceptAccept mocks base method +// InterceptAccept mocks base method. func (m *MockConnectionGater) InterceptAccept(arg0 network.ConnMultiaddrs) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "InterceptAccept", arg0) @@ -45,13 +45,13 @@ func (m *MockConnectionGater) InterceptAccept(arg0 network.ConnMultiaddrs) bool return ret0 } -// InterceptAccept indicates an expected call of InterceptAccept +// InterceptAccept indicates an expected call of InterceptAccept. func (mr *MockConnectionGaterMockRecorder) InterceptAccept(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InterceptAccept", reflect.TypeOf((*MockConnectionGater)(nil).InterceptAccept), arg0) } -// InterceptAddrDial mocks base method +// InterceptAddrDial mocks base method. func (m *MockConnectionGater) InterceptAddrDial(arg0 peer.ID, arg1 multiaddr.Multiaddr) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "InterceptAddrDial", arg0, arg1) @@ -59,13 +59,13 @@ func (m *MockConnectionGater) InterceptAddrDial(arg0 peer.ID, arg1 multiaddr.Mul return ret0 } -// InterceptAddrDial indicates an expected call of InterceptAddrDial +// InterceptAddrDial indicates an expected call of InterceptAddrDial. func (mr *MockConnectionGaterMockRecorder) InterceptAddrDial(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InterceptAddrDial", reflect.TypeOf((*MockConnectionGater)(nil).InterceptAddrDial), arg0, arg1) } -// InterceptPeerDial mocks base method +// InterceptPeerDial mocks base method. func (m *MockConnectionGater) InterceptPeerDial(arg0 peer.ID) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "InterceptPeerDial", arg0) @@ -73,13 +73,13 @@ func (m *MockConnectionGater) InterceptPeerDial(arg0 peer.ID) bool { return ret0 } -// InterceptPeerDial indicates an expected call of InterceptPeerDial +// InterceptPeerDial indicates an expected call of InterceptPeerDial. func (mr *MockConnectionGaterMockRecorder) InterceptPeerDial(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InterceptPeerDial", reflect.TypeOf((*MockConnectionGater)(nil).InterceptPeerDial), arg0) } -// InterceptSecured mocks base method +// InterceptSecured mocks base method. func (m *MockConnectionGater) InterceptSecured(arg0 network.Direction, arg1 peer.ID, arg2 network.ConnMultiaddrs) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "InterceptSecured", arg0, arg1, arg2) @@ -87,13 +87,13 @@ func (m *MockConnectionGater) InterceptSecured(arg0 network.Direction, arg1 peer return ret0 } -// InterceptSecured indicates an expected call of InterceptSecured +// InterceptSecured indicates an expected call of InterceptSecured. func (mr *MockConnectionGaterMockRecorder) InterceptSecured(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InterceptSecured", reflect.TypeOf((*MockConnectionGater)(nil).InterceptSecured), arg0, arg1, arg2) } -// InterceptUpgraded mocks base method +// InterceptUpgraded mocks base method. func (m *MockConnectionGater) InterceptUpgraded(arg0 network.Conn) (bool, control.DisconnectReason) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "InterceptUpgraded", arg0) @@ -102,7 +102,7 @@ func (m *MockConnectionGater) InterceptUpgraded(arg0 network.Conn) (bool, contro return ret0, ret1 } -// InterceptUpgraded indicates an expected call of InterceptUpgraded +// InterceptUpgraded indicates an expected call of InterceptUpgraded. func (mr *MockConnectionGaterMockRecorder) InterceptUpgraded(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InterceptUpgraded", reflect.TypeOf((*MockConnectionGater)(nil).InterceptUpgraded), arg0) diff --git a/p2p/transport/quic/tracer.go b/p2p/transport/quic/tracer.go index d46a4a9867..4b843a4a9a 100644 --- a/p2p/transport/quic/tracer.go +++ b/p2p/transport/quic/tracer.go @@ -10,14 +10,13 @@ import ( "github.com/klauspost/compress/zstd" "github.com/lucas-clemente/quic-go/logging" - "github.com/lucas-clemente/quic-go/metrics" "github.com/lucas-clemente/quic-go/qlog" ) var tracer logging.Tracer func init() { - tracers := []logging.Tracer{metrics.NewTracer()} + tracers := []logging.Tracer{} if qlogDir := os.Getenv("QLOGDIR"); len(qlogDir) > 0 { if qlogger := initQlogger(qlogDir); qlogger != nil { tracers = append(tracers, qlogger) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 2be59bc2eb..17ee0c19a1 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -30,10 +30,10 @@ var log = logging.Logger("quic-transport") var quicDialContext = quic.DialContext // so we can mock it in tests var quicConfig = &quic.Config{ - MaxIncomingStreams: 1000, - MaxIncomingUniStreams: -1, // disable unidirectional streams - MaxReceiveStreamFlowControlWindow: 10 * (1 << 20), // 10 MB - MaxReceiveConnectionFlowControlWindow: 15 * (1 << 20), // 15 MB + MaxIncomingStreams: 1000, + MaxIncomingUniStreams: -1, // disable unidirectional streams + MaxStreamReceiveWindow: 10 * (1 << 20), // 10 MB + MaxConnectionReceiveWindow: 15 * (1 << 20), // 15 MB AcceptToken: func(clientAddr net.Addr, _ *quic.Token) bool { // TODO(#6): require source address validation when under load return true From 26f5509e94a7390fc61993c7462aeaee82f1d0e6 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 13 Mar 2021 17:45:50 +0800 Subject: [PATCH 105/138] expose some Prometheus metrics --- p2p/transport/quic/tracer.go | 2 +- p2p/transport/quic/tracer_metrics.go | 369 +++++++++++++++++++++++++++ 2 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 p2p/transport/quic/tracer_metrics.go diff --git a/p2p/transport/quic/tracer.go b/p2p/transport/quic/tracer.go index 4b843a4a9a..c5bbf71493 100644 --- a/p2p/transport/quic/tracer.go +++ b/p2p/transport/quic/tracer.go @@ -16,7 +16,7 @@ import ( var tracer logging.Tracer func init() { - tracers := []logging.Tracer{} + tracers := []logging.Tracer{&metricsTracer{}} if qlogDir := os.Getenv("QLOGDIR"); len(qlogDir) > 0 { if qlogger := initQlogger(qlogDir); qlogger != nil { tracers = append(tracers, qlogger) diff --git a/p2p/transport/quic/tracer_metrics.go b/p2p/transport/quic/tracer_metrics.go new file mode 100644 index 0000000000..376d346074 --- /dev/null +++ b/p2p/transport/quic/tracer_metrics.go @@ -0,0 +1,369 @@ +package libp2pquic + +import ( + "net" + "sync" + "time" + + "github.com/lucas-clemente/quic-go/logging" + "github.com/prometheus/client_golang/prometheus" +) + +var ( + bytesTransferred *prometheus.CounterVec + newConns *prometheus.CounterVec + closedConns *prometheus.CounterVec + sentPackets *prometheus.CounterVec + rcvdPackets *prometheus.CounterVec + bufferedPackets *prometheus.CounterVec + droppedPackets *prometheus.CounterVec + lostPackets *prometheus.CounterVec + connErrors *prometheus.CounterVec +) + +type aggregatingCollector struct { + mutex sync.Mutex + + conns map[string] /* conn ID */ *metricsConnTracer + rtts prometheus.Histogram + connDurations prometheus.Histogram +} + +func newAggregatingCollector() *aggregatingCollector { + return &aggregatingCollector{ + conns: make(map[string]*metricsConnTracer), + rtts: prometheus.NewHistogram(prometheus.HistogramOpts{ + Name: "quic_smoothed_rtt", + Help: "Smoothed RTT", + Buckets: prometheus.ExponentialBuckets(0.001, 1.25, 40), // 1ms to ~6000ms + }), + connDurations: prometheus.NewHistogram(prometheus.HistogramOpts{ + Name: "quic_connection_duration", + Help: "Connection Duration", + Buckets: prometheus.ExponentialBuckets(1, 1.5, 40), // 1s to ~12 weeks + }), + } +} + +var _ prometheus.Collector = &aggregatingCollector{} + +func (c *aggregatingCollector) Describe(descs chan<- *prometheus.Desc) { + descs <- c.rtts.Desc() + descs <- c.connDurations.Desc() +} + +func (c *aggregatingCollector) Collect(metrics chan<- prometheus.Metric) { + now := time.Now() + c.mutex.Lock() + for _, conn := range c.conns { + if rtt, valid := conn.getSmoothedRTT(); valid { + c.rtts.Observe(rtt.Seconds()) + } + c.connDurations.Observe(now.Sub(conn.startTime).Seconds()) + } + c.mutex.Unlock() + metrics <- c.rtts + metrics <- c.connDurations +} + +func (c *aggregatingCollector) AddConn(id string, t *metricsConnTracer) { + c.mutex.Lock() + c.conns[id] = t + c.mutex.Unlock() +} + +func (c *aggregatingCollector) RemoveConn(id string) { + c.mutex.Lock() + delete(c.conns, id) + c.mutex.Unlock() +} + +var collector *aggregatingCollector + +func init() { + const ( + direction = "direction" + encLevel = "encryption_level" + ) + + closedConns = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "quic_closed_connections", + Help: "closed QUIC connection", + }, + []string{direction}, + ) + prometheus.MustRegister(closedConns) + newConns = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "quic_new_connections", + Help: "new QUIC connection", + }, + []string{direction, "handshake_successful"}, + ) + prometheus.MustRegister(newConns) + bytesTransferred = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "quic_bytes_transferred", + Help: "QUIC bytes transferred", + }, + []string{direction}, // TODO: this is confusing. Other times, we use direction for the perspective + ) + prometheus.MustRegister(bytesTransferred) + sentPackets = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "quic_sent_packets", + Help: "QUIC packets sent", + }, + []string{encLevel}, + ) + prometheus.MustRegister(sentPackets) + rcvdPackets = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "quic_rcvd_packets", + Help: "QUIC packets received", + }, + []string{encLevel}, + ) + prometheus.MustRegister(rcvdPackets) + bufferedPackets = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "quic_buffered_packets", + Help: "Buffered packets", + }, + []string{"packet_type"}, + ) + prometheus.MustRegister(bufferedPackets) + droppedPackets = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "quic_dropped_packets", + Help: "Dropped packets", + }, + []string{"packet_type", "reason"}, + ) + prometheus.MustRegister(droppedPackets) + connErrors = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "quic_conn_errors", + Help: "QUIC connection errors", + }, + []string{"side", "error_code"}, + ) + prometheus.MustRegister(connErrors) + lostPackets = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "quic_lost_packets", + Help: "QUIC lost received", + }, + []string{encLevel, "reason"}, + ) + prometheus.MustRegister(lostPackets) + collector = newAggregatingCollector() + prometheus.MustRegister(collector) +} + +type metricsTracer struct{} + +func (m *metricsTracer) TracerForConnection(p logging.Perspective, connID logging.ConnectionID) logging.ConnectionTracer { + return &metricsConnTracer{perspective: p, connID: connID} +} + +func (m *metricsTracer) SentPacket(_ net.Addr, _ *logging.Header, size logging.ByteCount, _ []logging.Frame) { + bytesTransferred.WithLabelValues("sent").Add(float64(size)) +} + +func (m *metricsTracer) DroppedPacket(addr net.Addr, packetType logging.PacketType, count logging.ByteCount, reason logging.PacketDropReason) { +} + +type metricsConnTracer struct { + perspective logging.Perspective + startTime time.Time + connID logging.ConnectionID + handshakeComplete bool + + mutex sync.Mutex + numRTTMeasurements int + rtt time.Duration +} + +var _ logging.ConnectionTracer = &metricsConnTracer{} + +func (m *metricsConnTracer) getDirection() string { + if m.perspective == logging.PerspectiveClient { + return "outgoing" + } + return "incoming" +} + +func (m *metricsConnTracer) getEncLevel(packetType logging.PacketType) string { + switch packetType { + case logging.PacketType0RTT: + return "0-RTT" + case logging.PacketTypeInitial: + return "Initial" + case logging.PacketTypeHandshake: + return "Handshake" + case logging.PacketTypeRetry: + return "Retry" + case logging.PacketType1RTT: + return "1-RTT" + default: + return "unknown" + } +} + +func (m *metricsConnTracer) StartedConnection(net.Addr, net.Addr, logging.VersionNumber, logging.ConnectionID, logging.ConnectionID) { + m.startTime = time.Now() + collector.AddConn(m.connID.String(), m) +} + +func (m *metricsConnTracer) ClosedConnection(r logging.CloseReason) { + if _, _, ok := r.ApplicationError(); ok { + return + } + var desc string + side := "local" + if _, ok := r.StatelessReset(); ok { + side = "remote" + desc = "stateless_reset" + } + if timeout, ok := r.Timeout(); ok { + switch timeout { + case logging.TimeoutReasonHandshake: + desc = "handshake_timeout" + case logging.TimeoutReasonIdle: + desc = "idle_timeout" + default: + desc = "unknown timeout" + } + } + if code, remote, ok := r.TransportError(); ok { + if code == 0xc { // ignore APPLICATION_ERROR + return + } + if remote { + side = "remote" + } + desc = code.String() + } + connErrors.WithLabelValues(side, desc).Inc() +} +func (m *metricsConnTracer) SentTransportParameters(parameters *logging.TransportParameters) {} +func (m *metricsConnTracer) ReceivedTransportParameters(parameters *logging.TransportParameters) {} +func (m *metricsConnTracer) RestoredTransportParameters(parameters *logging.TransportParameters) {} +func (m *metricsConnTracer) SentPacket(hdr *logging.ExtendedHeader, size logging.ByteCount, _ *logging.AckFrame, _ []logging.Frame) { + bytesTransferred.WithLabelValues("sent").Add(float64(size)) + sentPackets.WithLabelValues(m.getEncLevel(logging.PacketTypeFromHeader(&hdr.Header))).Inc() +} + +func (m *metricsConnTracer) ReceivedVersionNegotiationPacket(hdr *logging.Header, v []logging.VersionNumber) { + bytesTransferred.WithLabelValues("rcvd").Add(float64(hdr.ParsedLen() + logging.ByteCount(4*len(v)))) + rcvdPackets.WithLabelValues("Version Negotiation").Inc() +} + +func (m *metricsConnTracer) ReceivedRetry(*logging.Header) { + rcvdPackets.WithLabelValues("Retry").Inc() +} + +func (m *metricsConnTracer) ReceivedPacket(hdr *logging.ExtendedHeader, size logging.ByteCount, _ []logging.Frame) { + bytesTransferred.WithLabelValues("rcvd").Add(float64(size)) + rcvdPackets.WithLabelValues(m.getEncLevel(logging.PacketTypeFromHeader(&hdr.Header))).Inc() +} + +func (m *metricsConnTracer) BufferedPacket(packetType logging.PacketType) { + bufferedPackets.WithLabelValues(m.getEncLevel(packetType)).Inc() +} + +func (m *metricsConnTracer) DroppedPacket(packetType logging.PacketType, size logging.ByteCount, r logging.PacketDropReason) { + bytesTransferred.WithLabelValues("rcvd").Add(float64(size)) + var reason string + switch r { + case logging.PacketDropKeyUnavailable: + reason = "key_unavailable" + case logging.PacketDropUnknownConnectionID: + reason = "unknown_connection_id" + case logging.PacketDropHeaderParseError: + reason = "header_parse_error" + case logging.PacketDropPayloadDecryptError: + reason = "payload_decrypt_error" + case logging.PacketDropProtocolViolation: + reason = "protocol_violation" + case logging.PacketDropDOSPrevention: + reason = "dos_prevention" + case logging.PacketDropUnsupportedVersion: + reason = "unsupported_version" + case logging.PacketDropUnexpectedPacket: + reason = "unexpected_packet" + case logging.PacketDropUnexpectedSourceConnectionID: + reason = "unexpected_source_connection_id" + case logging.PacketDropUnexpectedVersion: + reason = "unexpected_version" + case logging.PacketDropDuplicate: + reason = "duplicate" + default: + reason = "unknown" + } + droppedPackets.WithLabelValues(m.getEncLevel(packetType), reason).Inc() +} + +func (m *metricsConnTracer) UpdatedMetrics(rttStats *logging.RTTStats, cwnd, bytesInFlight logging.ByteCount, packetsInFlight int) { + m.mutex.Lock() + m.rtt = rttStats.SmoothedRTT() + m.numRTTMeasurements++ + m.mutex.Unlock() +} + +func (m *metricsConnTracer) LostPacket(level logging.EncryptionLevel, _ logging.PacketNumber, r logging.PacketLossReason) { + var reason string + switch r { + case logging.PacketLossReorderingThreshold: + reason = "reordering_threshold" + case logging.PacketLossTimeThreshold: + reason = "time_threshold" + default: + reason = "unknown" + } + lostPackets.WithLabelValues(level.String(), reason).Inc() +} + +func (m *metricsConnTracer) UpdatedCongestionState(state logging.CongestionState) {} +func (m *metricsConnTracer) UpdatedPTOCount(value uint32) {} +func (m *metricsConnTracer) UpdatedKeyFromTLS(level logging.EncryptionLevel, perspective logging.Perspective) { +} +func (m *metricsConnTracer) UpdatedKey(generation logging.KeyPhase, remote bool) {} +func (m *metricsConnTracer) DroppedEncryptionLevel(level logging.EncryptionLevel) { + if level == logging.EncryptionHandshake { + m.handleHandshakeComplete() + } +} +func (m *metricsConnTracer) DroppedKey(generation logging.KeyPhase) {} +func (m *metricsConnTracer) SetLossTimer(timerType logging.TimerType, level logging.EncryptionLevel, time time.Time) { +} + +func (m *metricsConnTracer) LossTimerExpired(timerType logging.TimerType, level logging.EncryptionLevel) { +} +func (m *metricsConnTracer) LossTimerCanceled() {} + +func (m *metricsConnTracer) Close() { + if m.handshakeComplete { + closedConns.WithLabelValues(m.getDirection()).Inc() + } else { + newConns.WithLabelValues(m.getDirection(), "false").Inc() + } + collector.RemoveConn(m.connID.String()) +} + +func (m *metricsConnTracer) Debug(name, msg string) {} + +func (m *metricsConnTracer) handleHandshakeComplete() { + m.handshakeComplete = true + newConns.WithLabelValues(m.getDirection(), "true").Inc() +} + +func (m *metricsConnTracer) getSmoothedRTT() (rtt time.Duration, valid bool) { + m.mutex.Lock() + rtt = m.rtt + valid = m.numRTTMeasurements > 10 + m.mutex.Unlock() + return +} From 84acfa12b648723921a7906b386fcadb308e95cd Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 2 Apr 2021 19:44:35 +0700 Subject: [PATCH 106/138] rename metrics to adhere to naming conventions --- p2p/transport/quic/tracer_metrics.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/p2p/transport/quic/tracer_metrics.go b/p2p/transport/quic/tracer_metrics.go index 376d346074..773dfe9071 100644 --- a/p2p/transport/quic/tracer_metrics.go +++ b/p2p/transport/quic/tracer_metrics.go @@ -88,7 +88,7 @@ func init() { closedConns = prometheus.NewCounterVec( prometheus.CounterOpts{ - Name: "quic_closed_connections", + Name: "quic_connections_closed_total", Help: "closed QUIC connection", }, []string{direction}, @@ -96,7 +96,7 @@ func init() { prometheus.MustRegister(closedConns) newConns = prometheus.NewCounterVec( prometheus.CounterOpts{ - Name: "quic_new_connections", + Name: "quic_connections_new_total", Help: "new QUIC connection", }, []string{direction, "handshake_successful"}, @@ -104,7 +104,7 @@ func init() { prometheus.MustRegister(newConns) bytesTransferred = prometheus.NewCounterVec( prometheus.CounterOpts{ - Name: "quic_bytes_transferred", + Name: "quic_transferred_bytes", Help: "QUIC bytes transferred", }, []string{direction}, // TODO: this is confusing. Other times, we use direction for the perspective @@ -112,7 +112,7 @@ func init() { prometheus.MustRegister(bytesTransferred) sentPackets = prometheus.NewCounterVec( prometheus.CounterOpts{ - Name: "quic_sent_packets", + Name: "quic_packets_sent_total", Help: "QUIC packets sent", }, []string{encLevel}, @@ -120,7 +120,7 @@ func init() { prometheus.MustRegister(sentPackets) rcvdPackets = prometheus.NewCounterVec( prometheus.CounterOpts{ - Name: "quic_rcvd_packets", + Name: "quic_packets_rcvd_total", Help: "QUIC packets received", }, []string{encLevel}, @@ -128,7 +128,7 @@ func init() { prometheus.MustRegister(rcvdPackets) bufferedPackets = prometheus.NewCounterVec( prometheus.CounterOpts{ - Name: "quic_buffered_packets", + Name: "quic_packets_buffered_total", Help: "Buffered packets", }, []string{"packet_type"}, @@ -136,7 +136,7 @@ func init() { prometheus.MustRegister(bufferedPackets) droppedPackets = prometheus.NewCounterVec( prometheus.CounterOpts{ - Name: "quic_dropped_packets", + Name: "quic_packets_dropped_total", Help: "Dropped packets", }, []string{"packet_type", "reason"}, @@ -144,7 +144,7 @@ func init() { prometheus.MustRegister(droppedPackets) connErrors = prometheus.NewCounterVec( prometheus.CounterOpts{ - Name: "quic_conn_errors", + Name: "quic_connection_errors_total", Help: "QUIC connection errors", }, []string{"side", "error_code"}, @@ -152,7 +152,7 @@ func init() { prometheus.MustRegister(connErrors) lostPackets = prometheus.NewCounterVec( prometheus.CounterOpts{ - Name: "quic_lost_packets", + Name: "quic_packets_lost_total", Help: "QUIC lost received", }, []string{encLevel, "reason"}, From e2628386506ff0dc2c7291b59f33c0e72b00d920 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 3 Apr 2021 08:44:44 +0700 Subject: [PATCH 107/138] update quic-go to v0.20.1 --- p2p/transport/quic/tracer_metrics.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/p2p/transport/quic/tracer_metrics.go b/p2p/transport/quic/tracer_metrics.go index 773dfe9071..1eab55da73 100644 --- a/p2p/transport/quic/tracer_metrics.go +++ b/p2p/transport/quic/tracer_metrics.go @@ -212,7 +212,7 @@ func (m *metricsConnTracer) getEncLevel(packetType logging.PacketType) string { } } -func (m *metricsConnTracer) StartedConnection(net.Addr, net.Addr, logging.VersionNumber, logging.ConnectionID, logging.ConnectionID) { +func (m *metricsConnTracer) StartedConnection(net.Addr, net.Addr, logging.ConnectionID, logging.ConnectionID) { m.startTime = time.Now() collector.AddConn(m.connID.String(), m) } @@ -313,6 +313,8 @@ func (m *metricsConnTracer) UpdatedMetrics(rttStats *logging.RTTStats, cwnd, byt m.mutex.Unlock() } +func (m *metricsConnTracer) AcknowledgedPacket(logging.EncryptionLevel, logging.PacketNumber) {} + func (m *metricsConnTracer) LostPacket(level logging.EncryptionLevel, _ logging.PacketNumber, r logging.PacketLossReason) { var reason string switch r { From f1cfac9df26f89783a9ab9a079631d93672b0de7 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 6 Apr 2021 15:18:31 +0700 Subject: [PATCH 108/138] increase test timeout to reduce flakiness of test on Windows --- p2p/transport/quic/conn_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 12e0647e65..96559ccc36 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -284,8 +284,8 @@ var _ = Describe("Connection", func() { }(c) } - Eventually(done, 5*time.Second).Should(Receive()) - Eventually(done, 5*time.Second).Should(Receive()) + Eventually(done, 15*time.Second).Should(Receive()) + Eventually(done, 15*time.Second).Should(Receive()) }) It("sends stateless resets", func() { From 282be490609298667f3f1e451d5c4fca3a42ec9a Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 4 Apr 2021 13:06:43 +0700 Subject: [PATCH 109/138] correctly export version negotiation failures to Prometheus --- p2p/transport/quic/tracer_metrics.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/p2p/transport/quic/tracer_metrics.go b/p2p/transport/quic/tracer_metrics.go index 1eab55da73..929619d991 100644 --- a/p2p/transport/quic/tracer_metrics.go +++ b/p2p/transport/quic/tracer_metrics.go @@ -227,6 +227,9 @@ func (m *metricsConnTracer) ClosedConnection(r logging.CloseReason) { side = "remote" desc = "stateless_reset" } + if _, ok := r.VersionNegotiation(); ok { + desc = "version_negotiation" + } if timeout, ok := r.Timeout(); ok { switch timeout { case logging.TimeoutReasonHandshake: From cc611a688109ba75f3a50bc5b74587c19fa87237 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 4 May 2021 23:24:01 +0700 Subject: [PATCH 110/138] update quic-go to v0.21.0-rc2 --- p2p/transport/quic/stream.go | 12 ++--- p2p/transport/quic/tracer_metrics.go | 66 ++++++++++++++++------------ p2p/transport/quic/transport.go | 2 +- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/p2p/transport/quic/stream.go b/p2p/transport/quic/stream.go index 57ca2e9155..0c92020683 100644 --- a/p2p/transport/quic/stream.go +++ b/p2p/transport/quic/stream.go @@ -1,13 +1,15 @@ package libp2pquic import ( + "errors" + "github.com/libp2p/go-libp2p-core/mux" - quic "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go" ) const ( - reset quic.ErrorCode = 0 + reset quic.StreamErrorCode = 0 ) type stream struct { @@ -16,19 +18,17 @@ type stream struct { func (s *stream) Read(b []byte) (n int, err error) { n, err = s.Stream.Read(b) - if serr, ok := err.(quic.StreamError); ok && serr.Canceled() { + if err != nil && errors.Is(err, &quic.StreamError{}) { err = mux.ErrReset } - return n, err } func (s *stream) Write(b []byte) (n int, err error) { n, err = s.Stream.Write(b) - if serr, ok := err.(quic.StreamError); ok && serr.Canceled() { + if err != nil && errors.Is(err, &quic.StreamError{}) { err = mux.ErrReset } - return n, err } diff --git a/p2p/transport/quic/tracer_metrics.go b/p2p/transport/quic/tracer_metrics.go index 929619d991..c3483419a7 100644 --- a/p2p/transport/quic/tracer_metrics.go +++ b/p2p/transport/quic/tracer_metrics.go @@ -1,12 +1,17 @@ package libp2pquic import ( + "context" + "errors" + "fmt" "net" "sync" "time" - "github.com/lucas-clemente/quic-go/logging" "github.com/prometheus/client_golang/prometheus" + + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/logging" ) var ( @@ -164,7 +169,9 @@ func init() { type metricsTracer struct{} -func (m *metricsTracer) TracerForConnection(p logging.Perspective, connID logging.ConnectionID) logging.ConnectionTracer { +var _ logging.Tracer = &metricsTracer{} + +func (m *metricsTracer) TracerForConnection(_ context.Context, p logging.Perspective, connID logging.ConnectionID) logging.ConnectionTracer { return &metricsConnTracer{perspective: p, connID: connID} } @@ -217,37 +224,38 @@ func (m *metricsConnTracer) StartedConnection(net.Addr, net.Addr, logging.Connec collector.AddConn(m.connID.String(), m) } -func (m *metricsConnTracer) ClosedConnection(r logging.CloseReason) { - if _, _, ok := r.ApplicationError(); ok { +func (m *metricsConnTracer) NegotiatedVersion(chosen quic.VersionNumber, clientVersions []quic.VersionNumber, serverVersions []quic.VersionNumber) { +} + +func (m *metricsConnTracer) ClosedConnection(e error) { + var ( + transportErr *quic.TransportError + remote bool + desc string + ) + + switch { + case errors.Is(e, &quic.ApplicationError{}): return - } - var desc string - side := "local" - if _, ok := r.StatelessReset(); ok { - side = "remote" + case errors.As(e, &transportErr): + remote = transportErr.Remote + desc = transportErr.ErrorCode.String() + case errors.Is(e, &quic.StatelessResetError{}): + remote = true desc = "stateless_reset" - } - if _, ok := r.VersionNegotiation(); ok { + case errors.Is(e, &quic.VersionNegotiationError{}): desc = "version_negotiation" + case errors.Is(e, &quic.IdleTimeoutError{}): + desc = "idle_timeout" + case errors.Is(e, &quic.HandshakeTimeoutError{}): + desc = "handshake_timeout" + default: + desc = fmt.Sprintf("unknown error: %v", e) } - if timeout, ok := r.Timeout(); ok { - switch timeout { - case logging.TimeoutReasonHandshake: - desc = "handshake_timeout" - case logging.TimeoutReasonIdle: - desc = "idle_timeout" - default: - desc = "unknown timeout" - } - } - if code, remote, ok := r.TransportError(); ok { - if code == 0xc { // ignore APPLICATION_ERROR - return - } - if remote { - side = "remote" - } - desc = code.String() + + side := "local" + if remote { + side = "remote" } connErrors.WithLabelValues(side, desc).Inc() } diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 17ee0c19a1..63e9362cb6 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -39,7 +39,7 @@ var quicConfig = &quic.Config{ return true }, KeepAlive: true, - Versions: []quic.VersionNumber{quic.VersionDraft29, quic.VersionDraft32}, + Versions: []quic.VersionNumber{quic.VersionDraft29}, } const statelessResetKeyInfo = "libp2p quic stateless reset key" From 8e713b4ea30ecced484578ef8e1e0557880fc6a8 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 30 May 2021 21:31:38 -0700 Subject: [PATCH 111/138] update quic-go, enable QUIC v1 (RFC 9000) When dialing connections, we still use draft-29, but QUIC v1 is now enabled on the server side. Once a large enough fraction of the network has upgraded, we can switch to v1 for dialing as well. --- p2p/transport/quic/transport.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 63e9362cb6..7e92916717 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -19,7 +19,7 @@ import ( "github.com/libp2p/go-libp2p-core/pnet" tpt "github.com/libp2p/go-libp2p-core/transport" p2ptls "github.com/libp2p/go-libp2p-tls" - quic "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" mafmt "github.com/multiformats/go-multiaddr-fmt" manet "github.com/multiformats/go-multiaddr/net" @@ -39,7 +39,7 @@ var quicConfig = &quic.Config{ return true }, KeepAlive: true, - Versions: []quic.VersionNumber{quic.VersionDraft29}, + Versions: []quic.VersionNumber{quic.VersionDraft29, quic.Version1}, } const statelessResetKeyInfo = "libp2p quic stateless reset key" From 4ff2d36c67701cd252965dbbd255cc535590731b Mon Sep 17 00:00:00 2001 From: vyzo Date: Mon, 5 Jul 2021 03:00:27 +0300 Subject: [PATCH 112/138] add hole punching support (#194) --- p2p/transport/quic/conn_test.go | 53 ++++++++++++- p2p/transport/quic/listener.go | 18 ++++- p2p/transport/quic/transport.go | 110 ++++++++++++++++++++++++++- p2p/transport/quic/transport_test.go | 2 +- 4 files changed, 176 insertions(+), 7 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 96559ccc36..f96200815d 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -11,14 +11,14 @@ import ( "sync/atomic" "time" - gomock "github.com/golang/mock/gomock" ic "github.com/libp2p/go-libp2p-core/crypto" + n "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" tpt "github.com/libp2p/go-libp2p-core/transport" - quicproxy "github.com/lucas-clemente/quic-go/integrationtests/tools/proxy" ma "github.com/multiformats/go-multiaddr" + "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) @@ -345,4 +345,53 @@ var _ = Describe("Connection", func() { Expect(rerr).To(HaveOccurred()) Expect(rerr.Error()).To(ContainSubstring("received a stateless reset")) }) + + It("hole punches", func() { + t1, err := NewTransport(serverKey, nil, nil) + Expect(err).ToNot(HaveOccurred()) + laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + Expect(err).ToNot(HaveOccurred()) + ln1, err := t1.Listen(laddr) + Expect(err).ToNot(HaveOccurred()) + done1 := make(chan struct{}) + go func() { + defer GinkgoRecover() + defer close(done1) + if _, err := ln1.Accept(); err == nil { + Fail("didn't expect to accept any connections") + } + }() + + t2, err := NewTransport(clientKey, nil, nil) + Expect(err).ToNot(HaveOccurred()) + ln2, err := t2.Listen(laddr) + Expect(err).ToNot(HaveOccurred()) + done2 := make(chan struct{}) + go func() { + defer GinkgoRecover() + defer close(done2) + if _, err := ln2.Accept(); err == nil { + Fail("didn't expect to accept any connections") + } + }() + connChan := make(chan tpt.CapableConn) + go func() { + defer GinkgoRecover() + conn, err := t2.Dial(n.WithSimultaneousConnect(context.Background(), ""), ln1.Multiaddr(), serverID) + Expect(err).ToNot(HaveOccurred()) + connChan <- conn + }() + conn1, err := t1.Dial(n.WithSimultaneousConnect(context.Background(), ""), ln2.Multiaddr(), clientID) + Expect(err).ToNot(HaveOccurred()) + defer conn1.Close() + Expect(conn1.RemotePeer()).To(Equal(clientID)) + var conn2 tpt.CapableConn + Eventually(connChan).Should(Receive(&conn2)) + defer conn2.Close() + Expect(conn2.RemotePeer()).To(Equal(serverID)) + ln1.Close() + ln2.Close() + Eventually(done1).Should(BeClosed()) + Eventually(done2).Should(BeClosed()) + }) }) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index a61f312ed5..d91a2495cc 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -12,7 +12,7 @@ import ( p2ptls "github.com/libp2p/go-libp2p-tls" - quic "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" ) @@ -74,6 +74,21 @@ func (l *listener) Accept() (tpt.CapableConn, error) { sess.CloseWithError(errorCodeConnectionGating, "connection gated") continue } + + // return through active hole punching if any + key := holePunchKey{addr: sess.RemoteAddr().String(), peer: conn.remotePeerID} + var wasHolePunch bool + l.transport.holePunchingMx.Lock() + holePunch, ok := l.transport.holePunching[key] + if ok && !holePunch.fulfilled { + holePunch.connCh <- conn + wasHolePunch = true + holePunch.fulfilled = true + } + l.transport.holePunchingMx.Unlock() + if wasHolePunch { + continue + } return conn, nil } } @@ -92,6 +107,7 @@ func (l *listener) setupConn(sess quic.Session) (*conn, error) { if err != nil { return nil, err } + remoteMultiaddr, err := toQuicMultiaddr(sess.RemoteAddr()) if err != nil { return nil, err diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 7e92916717..b7d962e648 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -1,20 +1,23 @@ package libp2pquic import ( + "bytes" "context" "errors" "fmt" "io" + "math/rand" "net" - - "github.com/libp2p/go-libp2p-core/connmgr" - n "github.com/libp2p/go-libp2p-core/network" + "sync" + "time" "github.com/minio/sha256-simd" "golang.org/x/crypto/hkdf" logging "github.com/ipfs/go-log" + "github.com/libp2p/go-libp2p-core/connmgr" ic "github.com/libp2p/go-libp2p-core/crypto" + n "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/pnet" tpt "github.com/libp2p/go-libp2p-core/transport" @@ -27,8 +30,12 @@ import ( var log = logging.Logger("quic-transport") +var ErrHolePunching = errors.New("hole punching attempted; no active dial") + var quicDialContext = quic.DialContext // so we can mock it in tests +var HolePunchTimeout = 5 * time.Second + var quicConfig = &quic.Config{ MaxIncomingStreams: 1000, MaxIncomingUniStreams: -1, // disable unidirectional streams @@ -96,10 +103,23 @@ type transport struct { serverConfig *quic.Config clientConfig *quic.Config gater connmgr.ConnectionGater + + holePunchingMx sync.Mutex + holePunching map[holePunchKey]*activeHolePunch } var _ tpt.Transport = &transport{} +type holePunchKey struct { + addr string + peer peer.ID +} + +type activeHolePunch struct { + connCh chan tpt.CapableConn + fulfilled bool +} + // NewTransport creates a new QUIC transport func NewTransport(key ic.PrivKey, psk pnet.PSK, gater connmgr.ConnectionGater) (tpt.Transport, error) { if len(psk) > 0 { @@ -138,6 +158,7 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, gater connmgr.ConnectionGater) ( serverConfig: config, clientConfig: config.Clone(), gater: gater, + holePunching: make(map[holePunchKey]*activeHolePunch), }, nil } @@ -156,6 +177,13 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp return nil, err } tlsConf, keyCh := t.identity.ConfigForPeer(p) + + if simConnect, _ := n.GetSimultaneousConnect(ctx); simConnect { + if bytes.Compare([]byte(t.localPeer), []byte(p)) < 0 { + return t.holePunch(ctx, network, addr, p) + } + } + pconn, err := t.connManager.Dial(network, addr) if err != nil { return nil, err @@ -202,6 +230,82 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp return conn, nil } +func (t *transport) holePunch(ctx context.Context, network string, addr *net.UDPAddr, p peer.ID) (tpt.CapableConn, error) { + pconn, err := t.connManager.Dial(network, addr) + if err != nil { + return nil, err + } + defer pconn.DecreaseCount() + + ctx, cancel := context.WithTimeout(ctx, HolePunchTimeout) + defer cancel() + + key := holePunchKey{addr: addr.String(), peer: p} + t.holePunchingMx.Lock() + if _, ok := t.holePunching[key]; ok { + t.holePunchingMx.Unlock() + return nil, fmt.Errorf("already punching hole for %s", addr) + } + connCh := make(chan tpt.CapableConn, 1) + t.holePunching[key] = &activeHolePunch{connCh: connCh} + t.holePunchingMx.Unlock() + + var timer *time.Timer + defer func() { + if timer != nil { + timer.Stop() + } + }() + + payload := make([]byte, 64) + var punchErr error +loop: + for i := 0; ; i++ { + if _, err := rand.Read(payload); err != nil { + punchErr = err + break + } + if _, err := pconn.UDPConn.WriteToUDP(payload, addr); err != nil { + punchErr = err + break + } + + maxSleep := 10 * (i + 1) * (i + 1) // in ms + if maxSleep > 200 { + maxSleep = 200 + } + d := 10*time.Millisecond + time.Duration(rand.Intn(maxSleep))*time.Millisecond + if timer == nil { + timer = time.NewTimer(d) + } else { + timer.Reset(d) + } + select { + case c := <-connCh: + t.holePunchingMx.Lock() + delete(t.holePunching, key) + t.holePunchingMx.Unlock() + return c, nil + case <-timer.C: + case <-ctx.Done(): + punchErr = ErrHolePunching + break loop + } + } + // we only arrive here if punchErr != nil + t.holePunchingMx.Lock() + defer func() { + delete(t.holePunching, key) + t.holePunchingMx.Unlock() + }() + select { + case c := <-t.holePunching[key].connCh: + return c, nil + default: + return nil, punchErr + } +} + // Don't use mafmt.QUIC as we don't want to dial DNS addresses. Just /ip{4,6}/udp/quic var dialMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_UDP), mafmt.Base(ma.P_QUIC)) diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go index 226ec2f9c5..00685fce17 100644 --- a/p2p/transport/quic/transport_test.go +++ b/p2p/transport/quic/transport_test.go @@ -11,7 +11,7 @@ import ( ic "github.com/libp2p/go-libp2p-core/crypto" tpt "github.com/libp2p/go-libp2p-core/transport" - quic "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" . "github.com/onsi/ginkgo" From 5ce0d8c8c610d748c50425d7a7cc1b77685a1faa Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 5 Jul 2021 14:48:44 -0700 Subject: [PATCH 113/138] don't compare peer IDs when hole punching --- p2p/transport/quic/conn_test.go | 2 +- p2p/transport/quic/transport.go | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index f96200815d..5c202aec35 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -377,7 +377,7 @@ var _ = Describe("Connection", func() { connChan := make(chan tpt.CapableConn) go func() { defer GinkgoRecover() - conn, err := t2.Dial(n.WithSimultaneousConnect(context.Background(), ""), ln1.Multiaddr(), serverID) + conn, err := t2.Dial(context.Background(), ln1.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) connChan <- conn }() diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index b7d962e648..702f01ef6c 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -1,7 +1,6 @@ package libp2pquic import ( - "bytes" "context" "errors" "fmt" @@ -179,9 +178,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp tlsConf, keyCh := t.identity.ConfigForPeer(p) if simConnect, _ := n.GetSimultaneousConnect(ctx); simConnect { - if bytes.Compare([]byte(t.localPeer), []byte(p)) < 0 { - return t.holePunch(ctx, network, addr, p) - } + return t.holePunch(ctx, network, addr, p) } pconn, err := t.connManager.Dial(network, addr) From 8c7dcf7567a6fe6e58f297a2577d62db2454d3cf Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 7 Jul 2021 11:08:56 -0700 Subject: [PATCH 114/138] remove unused function parameters for the reuse struct --- p2p/transport/quic/reuse.go | 17 ++++++----------- p2p/transport/quic/reuse_test.go | 2 +- p2p/transport/quic/transport.go | 8 ++++---- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go index 3aa6940298..112b0ffcab 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse.go @@ -5,8 +5,6 @@ import ( "sync" "time" - "github.com/libp2p/go-libp2p-core/connmgr" - "github.com/libp2p/go-netroute" ) @@ -24,7 +22,7 @@ type reuseConn struct { unusedSince time.Time } -func newReuseConn(conn *net.UDPConn, gater connmgr.ConnectionGater) *reuseConn { +func newReuseConn(conn *net.UDPConn) *reuseConn { return &reuseConn{UDPConn: conn} } @@ -53,8 +51,6 @@ func (c *reuseConn) ShouldGarbageCollect(now time.Time) bool { type reuse struct { mutex sync.Mutex - gater connmgr.ConnectionGater - garbageCollectorRunning bool unicast map[string] /* IP.String() */ map[int] /* port */ *reuseConn @@ -62,9 +58,8 @@ type reuse struct { global map[int]*reuseConn } -func newReuse(gater connmgr.ConnectionGater) *reuse { +func newReuse() *reuse { return &reuse{ - gater: gater, unicast: make(map[string]map[int]*reuseConn), global: make(map[int]*reuseConn), } @@ -127,7 +122,7 @@ func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { r.mutex.Lock() defer r.mutex.Unlock() - conn, err := r.dialLocked(network, raddr, ip) + conn, err := r.dialLocked(network, ip) if err != nil { return nil, err } @@ -136,7 +131,7 @@ func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { return conn, nil } -func (r *reuse) dialLocked(network string, raddr *net.UDPAddr, source *net.IP) (*reuseConn, error) { +func (r *reuse) dialLocked(network string, source *net.IP) (*reuseConn, error) { if source != nil { // We already have at least one suitable connection... if conns, ok := r.unicast[source.String()]; ok { @@ -166,7 +161,7 @@ func (r *reuse) dialLocked(network string, raddr *net.UDPAddr, source *net.IP) ( if err != nil { return nil, err } - rconn := newReuseConn(conn, r.gater) + rconn := newReuseConn(conn) r.global[conn.LocalAddr().(*net.UDPAddr).Port] = rconn return rconn, nil } @@ -178,7 +173,7 @@ func (r *reuse) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { } localAddr := conn.LocalAddr().(*net.UDPAddr) - rconn := newReuseConn(conn, r.gater) + rconn := newReuseConn(conn) rconn.IncreaseCount() r.mutex.Lock() diff --git a/p2p/transport/quic/reuse_test.go b/p2p/transport/quic/reuse_test.go index 1072bd00be..65fea25827 100644 --- a/p2p/transport/quic/reuse_test.go +++ b/p2p/transport/quic/reuse_test.go @@ -45,7 +45,7 @@ var _ = Describe("Reuse", func() { var reuse *reuse BeforeEach(func() { - reuse = newReuse(nil) + reuse = newReuse() }) Context("creating and reusing connections", func() { diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 702f01ef6c..f5a71e2188 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -56,9 +56,9 @@ type connManager struct { reuseUDP6 *reuse } -func newConnManager(gater connmgr.ConnectionGater) (*connManager, error) { - reuseUDP4 := newReuse(gater) - reuseUDP6 := newReuse(gater) +func newConnManager() (*connManager, error) { + reuseUDP4 := newReuse() + reuseUDP6 := newReuse() return &connManager{ reuseUDP4: reuseUDP4, @@ -133,7 +133,7 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, gater connmgr.ConnectionGater) ( if err != nil { return nil, err } - connManager, err := newConnManager(gater) + connManager, err := newConnManager() if err != nil { return nil, err } From 2cef5be872d375187fdb2657811916b748c9b54e Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 7 Jul 2021 12:12:23 -0700 Subject: [PATCH 115/138] implement a Transport.Close that waits for the reuse's GC to finish --- p2p/transport/quic/conn_test.go | 20 ++++++ p2p/transport/quic/libp2pquic_suite_test.go | 13 +--- p2p/transport/quic/listener_test.go | 7 +- p2p/transport/quic/reuse.go | 72 ++++++++++++++------- p2p/transport/quic/reuse_test.go | 14 +++- p2p/transport/quic/transport.go | 11 ++++ p2p/transport/quic/transport_test.go | 5 ++ 7 files changed, 103 insertions(+), 39 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 5c202aec35..e45afe03d3 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -5,6 +5,7 @@ import ( "context" "crypto/rand" "fmt" + "io" "io/ioutil" mrand "math/rand" "net" @@ -70,11 +71,13 @@ var _ = Describe("Connection", func() { It("handshakes on IPv4", func() { serverTransport, err := NewTransport(serverKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer serverTransport.(io.Closer).Close() ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() clientTransport, err := NewTransport(clientKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer clientTransport.(io.Closer).Close() conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) defer conn.Close() @@ -94,11 +97,13 @@ var _ = Describe("Connection", func() { It("handshakes on IPv6", func() { serverTransport, err := NewTransport(serverKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer serverTransport.(io.Closer).Close() ln := runServer(serverTransport, "/ip6/::1/udp/0/quic") defer ln.Close() clientTransport, err := NewTransport(clientKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer clientTransport.(io.Closer).Close() conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) defer conn.Close() @@ -118,11 +123,13 @@ var _ = Describe("Connection", func() { It("opens and accepts streams", func() { serverTransport, err := NewTransport(serverKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer serverTransport.(io.Closer).Close() ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() clientTransport, err := NewTransport(clientKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer clientTransport.(io.Closer).Close() conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) defer conn.Close() @@ -147,6 +154,7 @@ var _ = Describe("Connection", func() { serverTransport, err := NewTransport(serverKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer serverTransport.(io.Closer).Close() ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") clientTransport, err := NewTransport(clientKey, nil, nil) @@ -154,6 +162,7 @@ var _ = Describe("Connection", func() { // dial, but expect the wrong peer ID _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), thirdPartyID) Expect(err).To(HaveOccurred()) + defer clientTransport.(io.Closer).Close() Expect(err.Error()).To(ContainSubstring("CRYPTO_ERROR")) done := make(chan struct{}) @@ -172,6 +181,7 @@ var _ = Describe("Connection", func() { cg.EXPECT().InterceptAccept(gomock.Any()) serverTransport, err := NewTransport(serverKey, nil, cg) Expect(err).ToNot(HaveOccurred()) + defer serverTransport.(io.Closer).Close() ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() @@ -185,6 +195,7 @@ var _ = Describe("Connection", func() { clientTransport, err := NewTransport(clientKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer clientTransport.(io.Closer).Close() // make sure that connection attempts fails conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) @@ -205,6 +216,7 @@ var _ = Describe("Connection", func() { It("gates secured connections", func() { serverTransport, err := NewTransport(serverKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer serverTransport.(io.Closer).Close() ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() @@ -213,6 +225,7 @@ var _ = Describe("Connection", func() { clientTransport, err := NewTransport(clientKey, nil, cg) Expect(err).ToNot(HaveOccurred()) + defer clientTransport.(io.Closer).Close() // make sure that connection attempts fails _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) @@ -232,10 +245,12 @@ var _ = Describe("Connection", func() { serverTransport, err := NewTransport(serverKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer serverTransport.(io.Closer).Close() ln1 := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln1.Close() serverTransport2, err := NewTransport(serverKey2, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer serverTransport2.(io.Closer).Close() ln2 := runServer(serverTransport2, "/ip4/127.0.0.1/udp/0/quic") defer ln2.Close() @@ -262,6 +277,7 @@ var _ = Describe("Connection", func() { clientTransport, err := NewTransport(clientKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer clientTransport.(io.Closer).Close() c1, err := clientTransport.Dial(context.Background(), ln1.Multiaddr(), serverID) Expect(err).ToNot(HaveOccurred()) defer c1.Close() @@ -291,6 +307,7 @@ var _ = Describe("Connection", func() { It("sends stateless resets", func() { serverTransport, err := NewTransport(serverKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer serverTransport.(io.Closer).Close() ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") var drop uint32 @@ -307,6 +324,7 @@ var _ = Describe("Connection", func() { // establish a connection clientTransport, err := NewTransport(clientKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer clientTransport.(io.Closer).Close() proxyAddr, err := toQuicMultiaddr(proxy.LocalAddr()) Expect(err).ToNot(HaveOccurred()) conn, err := clientTransport.Dial(context.Background(), proxyAddr, serverID) @@ -349,6 +367,7 @@ var _ = Describe("Connection", func() { It("hole punches", func() { t1, err := NewTransport(serverKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer t1.(io.Closer).Close() laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") Expect(err).ToNot(HaveOccurred()) ln1, err := t1.Listen(laddr) @@ -364,6 +383,7 @@ var _ = Describe("Connection", func() { t2, err := NewTransport(clientKey, nil, nil) Expect(err).ToNot(HaveOccurred()) + defer t2.(io.Closer).Close() ln2, err := t2.Listen(laddr) Expect(err).ToNot(HaveOccurred()) done2 := make(chan struct{}) diff --git a/p2p/transport/quic/libp2pquic_suite_test.go b/p2p/transport/quic/libp2pquic_suite_test.go index 0415fed75a..8bef2305b1 100644 --- a/p2p/transport/quic/libp2pquic_suite_test.go +++ b/p2p/transport/quic/libp2pquic_suite_test.go @@ -1,14 +1,11 @@ package libp2pquic import ( - "bytes" mrand "math/rand" - "runtime/pprof" - "strings" "testing" "time" - gomock "github.com/golang/mock/gomock" + "github.com/golang/mock/gomock" "github.com/lucas-clemente/quic-go" . "github.com/onsi/ginkgo" @@ -31,16 +28,9 @@ var ( mockCtrl *gomock.Controller ) -func isGarbageCollectorRunning() bool { - var b bytes.Buffer - pprof.Lookup("goroutine").WriteTo(&b, 1) - return strings.Contains(b.String(), "go-libp2p-quic-transport.(*reuse).runGarbageCollector") -} - var _ = BeforeEach(func() { mockCtrl = gomock.NewController(GinkgoT()) - Expect(isGarbageCollectorRunning()).To(BeFalse()) garbageCollectIntervalOrig = garbageCollectInterval maxUnusedDurationOrig = maxUnusedDuration garbageCollectInterval = 50 * time.Millisecond @@ -52,7 +42,6 @@ var _ = BeforeEach(func() { var _ = AfterEach(func() { mockCtrl.Finish() - Eventually(isGarbageCollectorRunning).Should(BeFalse()) garbageCollectInterval = garbageCollectIntervalOrig maxUnusedDuration = maxUnusedDurationOrig quicConfig = origQuicConfig diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index cb5836ec35..f8d2041f38 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -7,12 +7,13 @@ import ( "crypto/x509" "errors" "fmt" + "io" "net" "syscall" ic "github.com/libp2p/go-libp2p-core/crypto" tpt "github.com/libp2p/go-libp2p-core/transport" - quic "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" . "github.com/onsi/ginkgo" @@ -38,6 +39,10 @@ var _ = Describe("Listener", func() { Expect(err).ToNot(HaveOccurred()) }) + AfterEach(func() { + Expect(t.(io.Closer).Close()).To(Succeed()) + }) + It("uses a conn that can interface assert to a UDPConn for listening", func() { origQuicListen := quicListen defer func() { quicListen = origQuicListen }() diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go index 112b0ffcab..f8633a0137 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse.go @@ -53,6 +53,9 @@ type reuse struct { garbageCollectorRunning bool + closeChan chan struct{} + garbageCollectorStopChan chan struct{} + unicast map[string] /* IP.String() */ map[int] /* port */ *reuseConn // global contains connections that are listening on 0.0.0.0 / :: global map[int]*reuseConn @@ -60,45 +63,52 @@ type reuse struct { func newReuse() *reuse { return &reuse{ - unicast: make(map[string]map[int]*reuseConn), - global: make(map[int]*reuseConn), + unicast: make(map[string]map[int]*reuseConn), + global: make(map[int]*reuseConn), + closeChan: make(chan struct{}), } } func (r *reuse) runGarbageCollector() { + defer close(r.garbageCollectorStopChan) ticker := time.NewTicker(garbageCollectInterval) defer ticker.Stop() - for now := range ticker.C { - var shouldExit bool - r.mutex.Lock() - for key, conn := range r.global { - if conn.ShouldGarbageCollect(now) { - conn.Close() - delete(r.global, key) - } - } - for ukey, conns := range r.unicast { - for key, conn := range conns { + for { + select { + case <-r.closeChan: + return + case now := <-ticker.C: + var shouldExit bool + r.mutex.Lock() + for key, conn := range r.global { if conn.ShouldGarbageCollect(now) { conn.Close() - delete(conns, key) + delete(r.global, key) } } - if len(conns) == 0 { - delete(r.unicast, ukey) + for ukey, conns := range r.unicast { + for key, conn := range conns { + if conn.ShouldGarbageCollect(now) { + conn.Close() + delete(conns, key) + } + } + if len(conns) == 0 { + delete(r.unicast, ukey) + } } - } - // stop the garbage collector if we're not tracking any connections - if len(r.global) == 0 && len(r.unicast) == 0 { - r.garbageCollectorRunning = false - shouldExit = true - } - r.mutex.Unlock() + // stop the garbage collector if we're not tracking any connections + if len(r.global) == 0 && len(r.unicast) == 0 { + r.garbageCollectorRunning = false + shouldExit = true + } + r.mutex.Unlock() - if shouldExit { - return + if shouldExit { + return + } } } } @@ -107,6 +117,7 @@ func (r *reuse) runGarbageCollector() { func (r *reuse) maybeStartGarbageCollector() { if !r.garbageCollectorRunning { r.garbageCollectorRunning = true + r.garbageCollectorStopChan = make(chan struct{}) go r.runGarbageCollector() } } @@ -199,3 +210,14 @@ func (r *reuse) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { r.unicast[localAddr.IP.String()][localAddr.Port] = rconn return rconn, err } + +func (r *reuse) Close() error { + r.mutex.Lock() + defer r.mutex.Unlock() + close(r.closeChan) + if r.garbageCollectorRunning { + <-r.garbageCollectorStopChan + r.garbageCollectorRunning = false + } + return nil +} diff --git a/p2p/transport/quic/reuse_test.go b/p2p/transport/quic/reuse_test.go index 65fea25827..e7436a0e20 100644 --- a/p2p/transport/quic/reuse_test.go +++ b/p2p/transport/quic/reuse_test.go @@ -1,7 +1,10 @@ package libp2pquic import ( + "bytes" "net" + "runtime/pprof" + "strings" "time" "github.com/libp2p/go-netroute" @@ -30,7 +33,6 @@ func closeAllConns(reuse *reuse) { } } reuse.mutex.Unlock() - Eventually(isGarbageCollectorRunning).Should(BeFalse()) } func OnPlatformsWithRoutingTablesIt(description string, f interface{}) { @@ -48,6 +50,16 @@ var _ = Describe("Reuse", func() { reuse = newReuse() }) + AfterEach(func() { + Expect(reuse.Close()).To(Succeed()) + }) + + isGarbageCollectorRunning := func() bool { + var b bytes.Buffer + pprof.Lookup("goroutine").WriteTo(&b, 1) + return strings.Contains(b.String(), "go-libp2p-quic-transport.(*reuse).runGarbageCollector") + } + Context("creating and reusing connections", func() { AfterEach(func() { closeAllConns(reuse) }) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index f5a71e2188..5e9fa23b9e 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -93,6 +93,13 @@ func (c *connManager) Dial(network string, raddr *net.UDPAddr) (*reuseConn, erro return reuse.Dial(network, raddr) } +func (c *connManager) Close() error { + if err := c.reuseUDP6.Close(); err != nil { + return err + } + return c.reuseUDP4.Close() +} + // The Transport implements the tpt.Transport interface for QUIC connections. type transport struct { privKey ic.PrivKey @@ -346,3 +353,7 @@ func (t *transport) Protocols() []int { func (t *transport) String() string { return "QUIC" } + +func (t *transport) Close() error { + return t.connManager.Close() +} diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go index 00685fce17..5f52ce06a4 100644 --- a/p2p/transport/quic/transport_test.go +++ b/p2p/transport/quic/transport_test.go @@ -7,6 +7,7 @@ import ( "crypto/tls" "crypto/x509" "errors" + "io" "net" ic "github.com/libp2p/go-libp2p-core/crypto" @@ -30,6 +31,10 @@ var _ = Describe("Transport", func() { Expect(err).ToNot(HaveOccurred()) }) + AfterEach(func() { + Expect(t.(io.Closer).Close()).To(Succeed()) + }) + It("says if it can dial an address", func() { invalidAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234") Expect(err).ToNot(HaveOccurred()) From 2750bef9b55bae7df69035211620956fb8ab2543 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 17 Jul 2021 11:12:42 +0200 Subject: [PATCH 116/138] simplify reuse gc --- p2p/transport/quic/reuse.go | 50 ++++++----------------- p2p/transport/quic/reuse_test.go | 69 +++++++++++--------------------- 2 files changed, 36 insertions(+), 83 deletions(-) diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go index f8633a0137..e281948b54 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse.go @@ -51,10 +51,8 @@ func (c *reuseConn) ShouldGarbageCollect(now time.Time) bool { type reuse struct { mutex sync.Mutex - garbageCollectorRunning bool - - closeChan chan struct{} - garbageCollectorStopChan chan struct{} + closeChan chan struct{} + gcStopChan chan struct{} unicast map[string] /* IP.String() */ map[int] /* port */ *reuseConn // global contains connections that are listening on 0.0.0.0 / :: @@ -62,15 +60,18 @@ type reuse struct { } func newReuse() *reuse { - return &reuse{ - unicast: make(map[string]map[int]*reuseConn), - global: make(map[int]*reuseConn), - closeChan: make(chan struct{}), + r := &reuse{ + unicast: make(map[string]map[int]*reuseConn), + global: make(map[int]*reuseConn), + closeChan: make(chan struct{}), + gcStopChan: make(chan struct{}), } + go r.gc() + return r } -func (r *reuse) runGarbageCollector() { - defer close(r.garbageCollectorStopChan) +func (r *reuse) gc() { + defer close(r.gcStopChan) ticker := time.NewTicker(garbageCollectInterval) defer ticker.Stop() @@ -79,7 +80,6 @@ func (r *reuse) runGarbageCollector() { case <-r.closeChan: return case now := <-ticker.C: - var shouldExit bool r.mutex.Lock() for key, conn := range r.global { if conn.ShouldGarbageCollect(now) { @@ -98,29 +98,11 @@ func (r *reuse) runGarbageCollector() { delete(r.unicast, ukey) } } - - // stop the garbage collector if we're not tracking any connections - if len(r.global) == 0 && len(r.unicast) == 0 { - r.garbageCollectorRunning = false - shouldExit = true - } r.mutex.Unlock() - - if shouldExit { - return - } } } } -// must be called while holding the mutex -func (r *reuse) maybeStartGarbageCollector() { - if !r.garbageCollectorRunning { - r.garbageCollectorRunning = true - r.garbageCollectorStopChan = make(chan struct{}) - go r.runGarbageCollector() - } -} func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { var ip *net.IP if router, err := netroute.New(); err == nil { @@ -138,7 +120,6 @@ func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { return nil, err } conn.IncreaseCount() - r.maybeStartGarbageCollector() return conn, nil } @@ -190,8 +171,6 @@ func (r *reuse) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { r.mutex.Lock() defer r.mutex.Unlock() - r.maybeStartGarbageCollector() - // Deal with listen on a global address if localAddr.IP.IsUnspecified() { // The kernel already checked that the laddr is not already listen @@ -212,12 +191,7 @@ func (r *reuse) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { } func (r *reuse) Close() error { - r.mutex.Lock() - defer r.mutex.Unlock() close(r.closeChan) - if r.garbageCollectorRunning { - <-r.garbageCollectorStopChan - r.garbageCollectorRunning = false - } + <-r.gcStopChan return nil } diff --git a/p2p/transport/quic/reuse_test.go b/p2p/transport/quic/reuse_test.go index e7436a0e20..b43d23d498 100644 --- a/p2p/transport/quic/reuse_test.go +++ b/p2p/transport/quic/reuse_test.go @@ -51,15 +51,16 @@ var _ = Describe("Reuse", func() { }) AfterEach(func() { + isGarbageCollectorRunning := func() bool { + var b bytes.Buffer + pprof.Lookup("goroutine").WriteTo(&b, 1) + return strings.Contains(b.String(), "go-libp2p-quic-transport.(*reuse).gc") + } + Expect(reuse.Close()).To(Succeed()) + Expect(isGarbageCollectorRunning()).To(BeFalse()) }) - isGarbageCollectorRunning := func() bool { - var b bytes.Buffer - pprof.Lookup("goroutine").WriteTo(&b, 1) - return strings.Contains(b.String(), "go-libp2p-quic-transport.(*reuse).runGarbageCollector") - } - Context("creating and reusing connections", func() { AfterEach(func() { closeAllConns(reuse) }) @@ -126,54 +127,32 @@ var _ = Describe("Reuse", func() { }) }) - Context("garbage-collecting connections", func() { + It("garbage collects connections once they're not used any more for a certain time", func() { numGlobals := func() int { reuse.mutex.Lock() defer reuse.mutex.Unlock() return len(reuse.global) } - BeforeEach(func() { - maxUnusedDuration = 100 * time.Millisecond - }) + maxUnusedDuration = 100 * time.Millisecond - It("garbage collects connections once they're not used any more for a certain time", func() { - addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") - Expect(err).ToNot(HaveOccurred()) - lconn, err := reuse.Listen("udp4", addr) - Expect(err).ToNot(HaveOccurred()) - Expect(lconn.GetCount()).To(Equal(1)) + addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") + Expect(err).ToNot(HaveOccurred()) + lconn, err := reuse.Listen("udp4", addr) + Expect(err).ToNot(HaveOccurred()) + Expect(lconn.GetCount()).To(Equal(1)) - closeTime := time.Now() - lconn.DecreaseCount() + closeTime := time.Now() + lconn.DecreaseCount() - for { - num := numGlobals() - if closeTime.Add(maxUnusedDuration).Before(time.Now()) { - break - } - Expect(num).To(Equal(1)) - time.Sleep(2 * time.Millisecond) + for { + num := numGlobals() + if closeTime.Add(maxUnusedDuration).Before(time.Now()) { + break } - Eventually(numGlobals).Should(BeZero()) - }) - - It("only stops the garbage collector when there are no more connections", func() { - addr1, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") - Expect(err).ToNot(HaveOccurred()) - conn1, err := reuse.Listen("udp4", addr1) - Expect(err).ToNot(HaveOccurred()) - - addr2, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") - Expect(err).ToNot(HaveOccurred()) - conn2, err := reuse.Listen("udp4", addr2) - Expect(err).ToNot(HaveOccurred()) - - Eventually(isGarbageCollectorRunning).Should(BeTrue()) - conn1.DecreaseCount() - Consistently(isGarbageCollectorRunning, 2*maxUnusedDuration).Should(BeTrue()) - conn2.DecreaseCount() - Eventually(isGarbageCollectorRunning, 2*maxUnusedDuration).Should(BeFalse()) - }) + Expect(num).To(Equal(1)) + time.Sleep(2 * time.Millisecond) + } + Eventually(numGlobals).Should(BeZero()) }) }) From c5cfb13007cacc650fc2f385189d43645f422f73 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 23 Jul 2021 16:31:10 +0200 Subject: [PATCH 117/138] close all UDP connections when the reuse is closed --- p2p/transport/quic/reuse.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go index e281948b54..ba5dbb4bc7 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse.go @@ -193,5 +193,15 @@ func (r *reuse) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { func (r *reuse) Close() error { close(r.closeChan) <-r.gcStopChan + r.mutex.Lock() + for _, conn := range r.global { + conn.Close() + } + for _, conns := range r.unicast { + for _, conn := range conns { + conn.Close() + } + } + r.mutex.Unlock() return nil } From 0d2a0fa5eea4fb4c63048708b579235361a16d96 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 23 Jul 2021 17:36:01 +0200 Subject: [PATCH 118/138] move cleanup to gc --- p2p/transport/quic/reuse.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go index ba5dbb4bc7..851df991e2 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse.go @@ -71,7 +71,19 @@ func newReuse() *reuse { } func (r *reuse) gc() { - defer close(r.gcStopChan) + defer func() { + r.mutex.Lock() + for _, conn := range r.global { + conn.Close() + } + for _, conns := range r.unicast { + for _, conn := range conns { + conn.Close() + } + } + r.mutex.Unlock() + close(r.gcStopChan) + }() ticker := time.NewTicker(garbageCollectInterval) defer ticker.Stop() @@ -193,15 +205,5 @@ func (r *reuse) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { func (r *reuse) Close() error { close(r.closeChan) <-r.gcStopChan - r.mutex.Lock() - for _, conn := range r.global { - conn.Close() - } - for _, conns := range r.unicast { - for _, conn := range conns { - conn.Close() - } - } - r.mutex.Unlock() return nil } From 687835b7bd7d3fd40f467d4285a7548b002d571e Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 5 Aug 2021 10:49:40 +0200 Subject: [PATCH 119/138] fix closing of streams in example --- p2p/transport/quic/cmd/client/main.go | 5 +++-- p2p/transport/quic/cmd/server/main.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/p2p/transport/quic/cmd/client/main.go b/p2p/transport/quic/cmd/client/main.go index bc033e8718..c39ca8785e 100644 --- a/p2p/transport/quic/cmd/client/main.go +++ b/p2p/transport/quic/cmd/client/main.go @@ -9,7 +9,7 @@ import ( "os" ic "github.com/libp2p/go-libp2p-core/crypto" - peer "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/peer" libp2pquic "github.com/libp2p/go-libp2p-quic-transport" ma "github.com/multiformats/go-multiaddr" ) @@ -53,12 +53,13 @@ func run(raddr string, p string) error { if err != nil { return err } + defer str.Close() const msg = "Hello world!" log.Printf("Sending: %s\n", msg) if _, err := str.Write([]byte(msg)); err != nil { return err } - if err := str.Close(); err != nil { + if err := str.CloseWrite(); err != nil { return err } data, err := ioutil.ReadAll(str) diff --git a/p2p/transport/quic/cmd/server/main.go b/p2p/transport/quic/cmd/server/main.go index 3a5fe24281..ec995bd0ff 100644 --- a/p2p/transport/quic/cmd/server/main.go +++ b/p2p/transport/quic/cmd/server/main.go @@ -72,7 +72,7 @@ func handleConn(conn tpt.CapableConn) error { return err } log.Printf("Received: %s\n", data) - if _, err := str.Write([]byte(data)); err != nil { + if _, err := str.Write(data); err != nil { return err } return str.Close() From bee04c7c0502a60043f55de0ca316219e8aaf4f9 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 22 Aug 2021 17:53:50 +0100 Subject: [PATCH 120/138] read the client option from the simultaneous connect context --- p2p/transport/quic/conn_test.go | 12 ++++++++++-- p2p/transport/quic/listener.go | 2 +- p2p/transport/quic/transport.go | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index e45afe03d3..82e2d6281f 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -397,11 +397,19 @@ var _ = Describe("Connection", func() { connChan := make(chan tpt.CapableConn) go func() { defer GinkgoRecover() - conn, err := t2.Dial(context.Background(), ln1.Multiaddr(), serverID) + conn, err := t2.Dial( + n.WithSimultaneousConnect(context.Background(), false, ""), + ln1.Multiaddr(), + serverID, + ) Expect(err).ToNot(HaveOccurred()) connChan <- conn }() - conn1, err := t1.Dial(n.WithSimultaneousConnect(context.Background(), ""), ln2.Multiaddr(), clientID) + conn1, err := t1.Dial( + n.WithSimultaneousConnect(context.Background(), true, ""), + ln2.Multiaddr(), + clientID, + ) Expect(err).ToNot(HaveOccurred()) defer conn1.Close() Expect(conn1.RemotePeer()).To(Equal(clientID)) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index d91a2495cc..039f717c72 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -37,7 +37,7 @@ func newListener(rconn *reuseConn, t *transport, localPeer peer.ID, key ic.PrivK // Note that since we have no way of associating an incoming QUIC connection with // the peer ID calculated here, we don't actually receive the peer's public key // from the key chan. - conf, _ := identity.ConfigForAny() + conf, _ := identity.ConfigForPeer("") return conf, nil } ln, err := quicListen(rconn, &tlsConf, t.serverConfig) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 5e9fa23b9e..3f97ebbbdd 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -184,7 +184,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp } tlsConf, keyCh := t.identity.ConfigForPeer(p) - if simConnect, _ := n.GetSimultaneousConnect(ctx); simConnect { + if ok, isClient, _ := n.GetSimultaneousConnect(ctx); ok && !isClient { return t.holePunch(ctx, network, addr, p) } From 3253205d50bbdad78199f9e4d4d380a4a1c8cfc1 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 11 Oct 2021 22:59:45 +0200 Subject: [PATCH 121/138] fix error assertions in the tracer --- p2p/transport/quic/tracer_metrics.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/p2p/transport/quic/tracer_metrics.go b/p2p/transport/quic/tracer_metrics.go index c3483419a7..cbf2701fe6 100644 --- a/p2p/transport/quic/tracer_metrics.go +++ b/p2p/transport/quic/tracer_metrics.go @@ -229,25 +229,30 @@ func (m *metricsConnTracer) NegotiatedVersion(chosen quic.VersionNumber, clientV func (m *metricsConnTracer) ClosedConnection(e error) { var ( - transportErr *quic.TransportError - remote bool - desc string + applicationErr *quic.ApplicationError + transportErr *quic.TransportError + statelessResetErr *quic.StatelessResetError + vnErr *quic.VersionNegotiationError + idleTimeoutErr *quic.IdleTimeoutError + handshakeTimeoutErr *quic.HandshakeTimeoutError + remote bool + desc string ) switch { - case errors.Is(e, &quic.ApplicationError{}): + case errors.As(e, &applicationErr): return case errors.As(e, &transportErr): remote = transportErr.Remote desc = transportErr.ErrorCode.String() - case errors.Is(e, &quic.StatelessResetError{}): + case errors.As(e, &statelessResetErr): remote = true desc = "stateless_reset" - case errors.Is(e, &quic.VersionNegotiationError{}): + case errors.As(e, &vnErr): desc = "version_negotiation" - case errors.Is(e, &quic.IdleTimeoutError{}): + case errors.As(e, &idleTimeoutErr): desc = "idle_timeout" - case errors.Is(e, &quic.HandshakeTimeoutError{}): + case errors.As(e, &handshakeTimeoutErr): desc = "handshake_timeout" default: desc = fmt.Sprintf("unknown error: %v", e) From 5a1099bf7e7efcad92ae56ab686ddb361a851b69 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 27 Oct 2021 17:08:36 -0700 Subject: [PATCH 122/138] reuse the same router until we change listeners Technically, someone can set net.ipv4.ip_nonlocal_bind=1 to bind to non-existent IP addresses, but I'm comfortable making this change and seeing if anyone complains. I highly doubt it'll have any impact. If it doesn't work out, we have other options (e.g., catch the "invalid route" error and try again). fixes #238 --- p2p/transport/quic/reuse.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/p2p/transport/quic/reuse.go b/p2p/transport/quic/reuse.go index 851df991e2..43eb2cd361 100644 --- a/p2p/transport/quic/reuse.go +++ b/p2p/transport/quic/reuse.go @@ -5,6 +5,7 @@ import ( "sync" "time" + "github.com/google/gopacket/routing" "github.com/libp2p/go-netroute" ) @@ -54,6 +55,7 @@ type reuse struct { closeChan chan struct{} gcStopChan chan struct{} + routes routing.Router unicast map[string] /* IP.String() */ map[int] /* port */ *reuseConn // global contains connections that are listening on 0.0.0.0 / :: global map[int]*reuseConn @@ -108,6 +110,15 @@ func (r *reuse) gc() { } if len(conns) == 0 { delete(r.unicast, ukey) + // If we've dropped all connections with a unicast binding, + // assume our routes may have changed. + if len(r.unicast) == 0 { + r.routes = nil + } else { + // Ignore the error, there's nothing we can do about + // it. + r.routes, _ = netroute.New() + } } } r.mutex.Unlock() @@ -117,7 +128,15 @@ func (r *reuse) gc() { func (r *reuse) Dial(network string, raddr *net.UDPAddr) (*reuseConn, error) { var ip *net.IP - if router, err := netroute.New(); err == nil { + + // Only bother looking up the source address if we actually _have_ non 0.0.0.0 listeners. + // Otherwise, save some time. + + r.mutex.Lock() + router := r.routes + r.mutex.Unlock() + + if router != nil { _, _, src, err := router.Route(raddr.IP) if err == nil && !src.IsUnspecified() { ip = &src @@ -194,6 +213,9 @@ func (r *reuse) Listen(network string, laddr *net.UDPAddr) (*reuseConn, error) { // Deal with listen on a unicast address if _, ok := r.unicast[localAddr.IP.String()]; !ok { r.unicast[localAddr.IP.String()] = make(map[int]*reuseConn) + // Assume the system's routes may have changed if we're adding a new listener. + // Ignore the error, there's nothing we can do. + r.routes, _ = netroute.New() } // The kernel already checked that the laddr is not already listen From 79681c8cbadbbb84aee51587a5047d4a8f08298b Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 7 Dec 2021 11:17:48 +0400 Subject: [PATCH 123/138] chore: update go-log to v2 (#242) --- p2p/transport/quic/transport.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 3f97ebbbdd..7925543de5 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -13,7 +13,7 @@ import ( "github.com/minio/sha256-simd" "golang.org/x/crypto/hkdf" - logging "github.com/ipfs/go-log" + logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p-core/connmgr" ic "github.com/libp2p/go-libp2p-core/crypto" n "github.com/libp2p/go-libp2p-core/network" From 05bb49a4bf72dd6777081f7951dfd160f5d7a553 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 10 Dec 2021 14:52:18 +0400 Subject: [PATCH 124/138] reduce the maximum number of incoming streams to 256 (#243) --- p2p/transport/quic/transport.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 7925543de5..13efc95193 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -36,7 +36,7 @@ var quicDialContext = quic.DialContext // so we can mock it in tests var HolePunchTimeout = 5 * time.Second var quicConfig = &quic.Config{ - MaxIncomingStreams: 1000, + MaxIncomingStreams: 256, MaxIncomingUniStreams: -1, // disable unidirectional streams MaxStreamReceiveWindow: 10 * (1 << 20), // 10 MB MaxConnectionReceiveWindow: 15 * (1 << 20), // 15 MB From 0e7e78b7bff5a7a3f975b022580b42d4be446108 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 25 Jul 2021 18:05:29 +0200 Subject: [PATCH 125/138] migrate the transport tests away from Ginkgo --- p2p/transport/quic/transport_test.go | 126 ++++++++++++++++----------- 1 file changed, 74 insertions(+), 52 deletions(-) diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go index 5f52ce06a4..6ea09b6dc5 100644 --- a/p2p/transport/quic/transport_test.go +++ b/p2p/transport/quic/transport_test.go @@ -9,69 +9,91 @@ import ( "errors" "io" "net" + "testing" + + "github.com/stretchr/testify/require" ic "github.com/libp2p/go-libp2p-core/crypto" tpt "github.com/libp2p/go-libp2p-core/transport" - "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "github.com/lucas-clemente/quic-go" ) -var _ = Describe("Transport", func() { - var t tpt.Transport +func getTransport(t *testing.T) tpt.Transport { + t.Helper() + rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + key, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(rsaKey)) + require.NoError(t, err) + tr, err := NewTransport(key, nil, nil) + require.NoError(t, err) + return tr +} - BeforeEach(func() { - rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) - Expect(err).ToNot(HaveOccurred()) - key, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(rsaKey)) - Expect(err).ToNot(HaveOccurred()) - t, err = NewTransport(key, nil, nil) - Expect(err).ToNot(HaveOccurred()) - }) +func TestQUICProtocol(t *testing.T) { + tr := getTransport(t) + defer tr.(io.Closer).Close() - AfterEach(func() { - Expect(t.(io.Closer).Close()).To(Succeed()) - }) + protocols := tr.Protocols() + if len(protocols) != 1 { + t.Fatalf("expected to only support a single protocol, got %v", protocols) + } + if protocols[0] != ma.P_QUIC { + t.Fatalf("expected the supported protocol to be QUIC, got %d", protocols[0]) + } +} - It("says if it can dial an address", func() { - invalidAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234") - Expect(err).ToNot(HaveOccurred()) - validAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/1234/quic") - Expect(err).ToNot(HaveOccurred()) - Expect(t.CanDial(invalidAddr)).To(BeFalse()) - Expect(t.CanDial(validAddr)).To(BeTrue()) - }) +func TestCanDial(t *testing.T) { + tr := getTransport(t) + defer tr.(io.Closer).Close() - It("says that it cannot dial /dns addresses", func() { - addr, err := ma.NewMultiaddr("/dns/google.com/udp/443/quic") - Expect(err).ToNot(HaveOccurred()) - Expect(t.CanDial(addr)).To(BeFalse()) - }) + invalid := []string{ + "/ip4/127.0.0.1/udp/1234", + "/ip4/5.5.5.5/tcp/1234", + "/dns/google.com/udp/443/quic", + } + valid := []string{ + "/ip4/127.0.0.1/udp/1234/quic", + "/ip4/5.5.5.5/udp/0/quic", + } + for _, s := range invalid { + invalidAddr, err := ma.NewMultiaddr(s) + require.NoError(t, err) + if tr.CanDial(invalidAddr) { + t.Errorf("didn't expect to be able to dial a non-quic address (%s)", invalidAddr) + } + } + for _, s := range valid { + validAddr, err := ma.NewMultiaddr(s) + require.NoError(t, err) + if !tr.CanDial(validAddr) { + t.Errorf("expected to be able to dial QUIC address (%s)", validAddr) + } + } +} - It("supports the QUIC protocol", func() { - protocols := t.Protocols() - Expect(protocols).To(HaveLen(1)) - Expect(protocols[0]).To(Equal(ma.P_QUIC)) - }) +// The connection passed to quic-go needs to be type-assertable to a net.UDPConn, +// in order to enable features like batch processing and ECN. +func TestConnectionPassedToQUIC(t *testing.T) { + tr := getTransport(t) + defer tr.(io.Closer).Close() - It("uses a conn that can interface assert to a UDPConn for dialing", func() { - origQuicDialContext := quicDialContext - defer func() { quicDialContext = origQuicDialContext }() + origQuicDialContext := quicDialContext + defer func() { quicDialContext = origQuicDialContext }() - var conn net.PacketConn - quicDialContext = func(_ context.Context, c net.PacketConn, _ net.Addr, _ string, _ *tls.Config, _ *quic.Config) (quic.Session, error) { - conn = c - return nil, errors.New("listen error") - } - remoteAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") - Expect(err).ToNot(HaveOccurred()) - _, err = t.Dial(context.Background(), remoteAddr, "remote peer id") - Expect(err).To(MatchError("listen error")) - Expect(conn).ToNot(BeNil()) - defer conn.Close() - _, ok := conn.(udpConn) - Expect(ok).To(BeTrue()) - }) -}) + var conn net.PacketConn + quicDialContext = func(_ context.Context, c net.PacketConn, _ net.Addr, _ string, _ *tls.Config, _ *quic.Config) (quic.Session, error) { + conn = c + return nil, errors.New("listen error") + } + remoteAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + require.NoError(t, err) + _, err = tr.Dial(context.Background(), remoteAddr, "remote peer id") + require.EqualError(t, err, "listen error") + require.NotNil(t, conn) + defer conn.Close() + if _, ok := conn.(udpConn); !ok { + t.Fatal("connection passed to quic-go cannot be type asserted to a *net.UDPConn") + } +} From 7a9ac66fdc5e53771a65bf86fb3281283cf8acf5 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 3 Jan 2022 17:26:32 +0400 Subject: [PATCH 126/138] migrate the conn tests away from Ginkgo --- p2p/transport/quic/conn_test.go | 688 ++++++++++---------- p2p/transport/quic/libp2pquic_suite_test.go | 6 - 2 files changed, 358 insertions(+), 336 deletions(-) diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 82e2d6281f..8c69b551da 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -10,6 +10,7 @@ import ( mrand "math/rand" "net" "sync/atomic" + "testing" "time" ic "github.com/libp2p/go-libp2p-core/crypto" @@ -20,406 +21,433 @@ import ( ma "github.com/multiformats/go-multiaddr" "github.com/golang/mock/gomock" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "github.com/stretchr/testify/require" ) //go:generate sh -c "mockgen -package libp2pquic -destination mock_connection_gater_test.go github.com/libp2p/go-libp2p-core/connmgr ConnectionGater && goimports -w mock_connection_gater_test.go" -var _ = Describe("Connection", func() { - var ( - serverKey, clientKey ic.PrivKey - serverID, clientID peer.ID - ) - - createPeer := func() (peer.ID, ic.PrivKey) { - var priv ic.PrivKey - var err error - switch mrand.Int() % 4 { - case 0: - fmt.Fprintf(GinkgoWriter, " using an ECDSA key: ") - priv, _, err = ic.GenerateECDSAKeyPair(rand.Reader) - case 1: - fmt.Fprintf(GinkgoWriter, " using an RSA key: ") - priv, _, err = ic.GenerateRSAKeyPair(2048, rand.Reader) - case 2: - fmt.Fprintf(GinkgoWriter, " using an Ed25519 key: ") - priv, _, err = ic.GenerateEd25519Key(rand.Reader) - case 3: - fmt.Fprintf(GinkgoWriter, " using an secp256k1 key: ") - priv, _, err = ic.GenerateSecp256k1Key(rand.Reader) - } - Expect(err).ToNot(HaveOccurred()) - id, err := peer.IDFromPrivateKey(priv) - Expect(err).ToNot(HaveOccurred()) - fmt.Fprintln(GinkgoWriter, id.Pretty()) - return id, priv - } - runServer := func(tr tpt.Transport, multiaddr string) tpt.Listener { - addr, err := ma.NewMultiaddr(multiaddr) - ExpectWithOffset(1, err).ToNot(HaveOccurred()) - ln, err := tr.Listen(addr) - ExpectWithOffset(1, err).ToNot(HaveOccurred()) - return ln +func createPeer(t *testing.T) (peer.ID, ic.PrivKey) { + var priv ic.PrivKey + var err error + switch mrand.Int() % 4 { + case 0: + priv, _, err = ic.GenerateECDSAKeyPair(rand.Reader) + case 1: + priv, _, err = ic.GenerateRSAKeyPair(2048, rand.Reader) + case 2: + priv, _, err = ic.GenerateEd25519Key(rand.Reader) + case 3: + priv, _, err = ic.GenerateSecp256k1Key(rand.Reader) } - - BeforeEach(func() { - serverID, serverKey = createPeer() - clientID, clientKey = createPeer() - }) - - It("handshakes on IPv4", func() { - serverTransport, err := NewTransport(serverKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) - defer serverTransport.(io.Closer).Close() - ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") - defer ln.Close() - + require.NoError(t, err) + id, err := peer.IDFromPrivateKey(priv) + require.NoError(t, err) + t.Logf("using a %s key: %s", priv.Type(), id.Pretty()) + return id, priv +} + +func runServer(t *testing.T, tr tpt.Transport, multiaddr string) tpt.Listener { + t.Helper() + addr, err := ma.NewMultiaddr(multiaddr) + require.NoError(t, err) + ln, err := tr.Listen(addr) + require.NoError(t, err) + return ln +} + +func TestHandshake(t *testing.T) { + serverID, serverKey := createPeer(t) + clientID, clientKey := createPeer(t) + serverTransport, err := NewTransport(serverKey, nil, nil) + require.NoError(t, err) + defer serverTransport.(io.Closer).Close() + + handshake := func(t *testing.T, ln tpt.Listener) { clientTransport, err := NewTransport(clientKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) + require.NoError(t, err) defer clientTransport.(io.Closer).Close() conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) - Expect(err).ToNot(HaveOccurred()) + require.NoError(t, err) defer conn.Close() serverConn, err := ln.Accept() - Expect(err).ToNot(HaveOccurred()) + require.NoError(t, err) defer serverConn.Close() - Expect(conn.LocalPeer()).To(Equal(clientID)) - Expect(conn.LocalPrivateKey()).To(Equal(clientKey)) - Expect(conn.RemotePeer()).To(Equal(serverID)) - Expect(conn.RemotePublicKey().Equals(serverKey.GetPublic())).To(BeTrue()) - Expect(serverConn.LocalPeer()).To(Equal(serverID)) - Expect(serverConn.LocalPrivateKey()).To(Equal(serverKey)) - Expect(serverConn.RemotePeer()).To(Equal(clientID)) - Expect(serverConn.RemotePublicKey().Equals(clientKey.GetPublic())).To(BeTrue()) - }) - It("handshakes on IPv6", func() { - serverTransport, err := NewTransport(serverKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) - defer serverTransport.(io.Closer).Close() - ln := runServer(serverTransport, "/ip6/::1/udp/0/quic") - defer ln.Close() + require.Equal(t, conn.LocalPeer(), clientID) + require.True(t, conn.LocalPrivateKey().Equals(clientKey), "local private key doesn't match") + require.Equal(t, conn.RemotePeer(), serverID) + require.True(t, conn.RemotePublicKey().Equals(serverKey.GetPublic()), "remote public key doesn't match") - clientTransport, err := NewTransport(clientKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) - defer clientTransport.(io.Closer).Close() - conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) - Expect(err).ToNot(HaveOccurred()) - defer conn.Close() - serverConn, err := ln.Accept() - Expect(err).ToNot(HaveOccurred()) - defer serverConn.Close() - Expect(conn.LocalPeer()).To(Equal(clientID)) - Expect(conn.LocalPrivateKey()).To(Equal(clientKey)) - Expect(conn.RemotePeer()).To(Equal(serverID)) - Expect(conn.RemotePublicKey().Equals(serverKey.GetPublic())).To(BeTrue()) - Expect(serverConn.LocalPeer()).To(Equal(serverID)) - Expect(serverConn.LocalPrivateKey()).To(Equal(serverKey)) - Expect(serverConn.RemotePeer()).To(Equal(clientID)) - Expect(serverConn.RemotePublicKey().Equals(clientKey.GetPublic())).To(BeTrue()) - }) + require.Equal(t, serverConn.LocalPeer(), serverID) + require.True(t, serverConn.LocalPrivateKey().Equals(serverKey), "local private key doesn't match") + require.Equal(t, serverConn.RemotePeer(), clientID) + require.True(t, serverConn.RemotePublicKey().Equals(clientKey.GetPublic()), "remote public key doesn't match") + } - It("opens and accepts streams", func() { - serverTransport, err := NewTransport(serverKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) - defer serverTransport.(io.Closer).Close() - ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") + t.Run("on IPv4", func(t *testing.T) { + ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() - - clientTransport, err := NewTransport(clientKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) - defer clientTransport.(io.Closer).Close() - conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) - Expect(err).ToNot(HaveOccurred()) - defer conn.Close() - serverConn, err := ln.Accept() - Expect(err).ToNot(HaveOccurred()) - defer serverConn.Close() - - str, err := conn.OpenStream(context.Background()) - Expect(err).ToNot(HaveOccurred()) - _, err = str.Write([]byte("foobar")) - Expect(err).ToNot(HaveOccurred()) - str.Close() - sstr, err := serverConn.AcceptStream() - Expect(err).ToNot(HaveOccurred()) - data, err := ioutil.ReadAll(sstr) - Expect(err).ToNot(HaveOccurred()) - Expect(data).To(Equal([]byte("foobar"))) + handshake(t, ln) }) - It("fails if the peer ID doesn't match", func() { - thirdPartyID, _ := createPeer() + t.Run("on IPv6", func(t *testing.T) { + ln := runServer(t, serverTransport, "/ip6/::1/udp/0/quic") + defer ln.Close() + handshake(t, ln) + }) +} + +func TestStreams(t *testing.T) { + serverID, serverKey := createPeer(t) + _, clientKey := createPeer(t) + + serverTransport, err := NewTransport(serverKey, nil, nil) + require.NoError(t, err) + defer serverTransport.(io.Closer).Close() + ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic") + defer ln.Close() + + clientTransport, err := NewTransport(clientKey, nil, nil) + require.NoError(t, err) + defer clientTransport.(io.Closer).Close() + conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) + require.NoError(t, err) + defer conn.Close() + serverConn, err := ln.Accept() + require.NoError(t, err) + defer serverConn.Close() + + str, err := conn.OpenStream(context.Background()) + require.NoError(t, err) + _, err = str.Write([]byte("foobar")) + require.NoError(t, err) + str.Close() + sstr, err := serverConn.AcceptStream() + require.NoError(t, err) + data, err := ioutil.ReadAll(sstr) + require.NoError(t, err) + require.Equal(t, data, []byte("foobar")) +} + +func TestHandshakeFailPeerIDMismatch(t *testing.T) { + _, serverKey := createPeer(t) + _, clientKey := createPeer(t) + thirdPartyID, _ := createPeer(t) + + serverTransport, err := NewTransport(serverKey, nil, nil) + require.NoError(t, err) + defer serverTransport.(io.Closer).Close() + ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic") + + clientTransport, err := NewTransport(clientKey, nil, nil) + require.NoError(t, err) + // dial, but expect the wrong peer ID + _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), thirdPartyID) + require.Error(t, err) + require.Contains(t, err.Error(), "CRYPTO_ERROR") + defer clientTransport.(io.Closer).Close() + + acceptErr := make(chan error) + go func() { + _, err := ln.Accept() + acceptErr <- err + }() + + select { + case <-acceptErr: + t.Fatal("didn't expect Accept to return before being closed") + case <-time.After(100 * time.Millisecond): + } - serverTransport, err := NewTransport(serverKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) - defer serverTransport.(io.Closer).Close() - ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") + require.NoError(t, ln.Close()) + require.Error(t, <-acceptErr) +} - clientTransport, err := NewTransport(clientKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) - // dial, but expect the wrong peer ID - _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), thirdPartyID) - Expect(err).To(HaveOccurred()) - defer clientTransport.(io.Closer).Close() - Expect(err.Error()).To(ContainSubstring("CRYPTO_ERROR")) +func TestConnectionGating(t *testing.T) { + serverID, serverKey := createPeer(t) + _, clientKey := createPeer(t) - done := make(chan struct{}) - go func() { - defer GinkgoRecover() - defer close(done) - ln.Accept() - }() - Consistently(done).ShouldNot(BeClosed()) - ln.Close() - Eventually(done).Should(BeClosed()) - }) + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + cg := NewMockConnectionGater(mockCtrl) - It("gates accepted connections", func() { - cg := NewMockConnectionGater(mockCtrl) - cg.EXPECT().InterceptAccept(gomock.Any()) + t.Run("accepted connections", func(t *testing.T) { serverTransport, err := NewTransport(serverKey, nil, cg) - Expect(err).ToNot(HaveOccurred()) defer serverTransport.(io.Closer).Close() - ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") + require.NoError(t, err) + ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() + cg.EXPECT().InterceptAccept(gomock.Any()) + accepted := make(chan struct{}) go func() { - defer GinkgoRecover() defer close(accepted) _, err := ln.Accept() - Expect(err).ToNot(HaveOccurred()) + require.NoError(t, err) }() clientTransport, err := NewTransport(clientKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) + require.NoError(t, err) defer clientTransport.(io.Closer).Close() // make sure that connection attempts fails conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) - Expect(err).ToNot(HaveOccurred()) + require.NoError(t, err) _, err = conn.AcceptStream() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("connection gated")) + require.Error(t, err) + require.Contains(t, err.Error(), "connection gated") // now allow the address and make sure the connection goes through cg.EXPECT().InterceptAccept(gomock.Any()).Return(true) cg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true) clientTransport.(*transport).clientConfig.HandshakeIdleTimeout = 2 * time.Second conn, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) - Expect(err).ToNot(HaveOccurred()) + require.NoError(t, err) defer conn.Close() - Eventually(accepted).Should(BeClosed()) + require.Eventually(t, func() bool { + select { + case <-accepted: + return true + default: + return false + } + }, time.Second, 10*time.Millisecond) }) - It("gates secured connections", func() { + t.Run("secured connections", func(t *testing.T) { serverTransport, err := NewTransport(serverKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) + require.NoError(t, err) defer serverTransport.(io.Closer).Close() - ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") + ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() cg := NewMockConnectionGater(mockCtrl) cg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()) clientTransport, err := NewTransport(clientKey, nil, cg) - Expect(err).ToNot(HaveOccurred()) + require.NoError(t, err) defer clientTransport.(io.Closer).Close() // make sure that connection attempts fails _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("connection gated")) + require.Error(t, err) + require.Contains(t, err.Error(), "connection gated") // now allow the peerId and make sure the connection goes through cg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()).Return(true) clientTransport.(*transport).clientConfig.HandshakeIdleTimeout = 2 * time.Second conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) - Expect(err).ToNot(HaveOccurred()) + require.NoError(t, err) conn.Close() }) - - It("dials to two servers at the same time", func() { - serverID2, serverKey2 := createPeer() - - serverTransport, err := NewTransport(serverKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) - defer serverTransport.(io.Closer).Close() - ln1 := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") - defer ln1.Close() - serverTransport2, err := NewTransport(serverKey2, nil, nil) - Expect(err).ToNot(HaveOccurred()) - defer serverTransport2.(io.Closer).Close() - ln2 := runServer(serverTransport2, "/ip4/127.0.0.1/udp/0/quic") - defer ln2.Close() - - data := bytes.Repeat([]byte{'a'}, 5*1<<20) // 5 MB - // wait for both servers to accept a connection - // then send some data - go func() { - serverConn1, err := ln1.Accept() - Expect(err).ToNot(HaveOccurred()) - serverConn2, err := ln2.Accept() - Expect(err).ToNot(HaveOccurred()) - - for _, c := range []tpt.CapableConn{serverConn1, serverConn2} { - go func(conn tpt.CapableConn) { - defer GinkgoRecover() - str, err := conn.OpenStream(context.Background()) - Expect(err).ToNot(HaveOccurred()) - defer str.Close() - _, err = str.Write(data) - Expect(err).ToNot(HaveOccurred()) - }(c) - } - }() - - clientTransport, err := NewTransport(clientKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) - defer clientTransport.(io.Closer).Close() - c1, err := clientTransport.Dial(context.Background(), ln1.Multiaddr(), serverID) - Expect(err).ToNot(HaveOccurred()) - defer c1.Close() - c2, err := clientTransport.Dial(context.Background(), ln2.Multiaddr(), serverID2) - Expect(err).ToNot(HaveOccurred()) - defer c2.Close() - - done := make(chan struct{}, 2) - // receive the data on both connections at the same time - for _, c := range []tpt.CapableConn{c1, c2} { +} + +func TestDialTwo(t *testing.T) { + serverID, serverKey := createPeer(t) + _, clientKey := createPeer(t) + serverID2, serverKey2 := createPeer(t) + + serverTransport, err := NewTransport(serverKey, nil, nil) + require.NoError(t, err) + defer serverTransport.(io.Closer).Close() + ln1 := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic") + defer ln1.Close() + serverTransport2, err := NewTransport(serverKey2, nil, nil) + require.NoError(t, err) + defer serverTransport2.(io.Closer).Close() + ln2 := runServer(t, serverTransport2, "/ip4/127.0.0.1/udp/0/quic") + defer ln2.Close() + + data := bytes.Repeat([]byte{'a'}, 5*1<<20) // 5 MB + // wait for both servers to accept a connection + // then send some data + go func() { + serverConn1, err := ln1.Accept() + require.NoError(t, err) + serverConn2, err := ln2.Accept() + require.NoError(t, err) + + for _, c := range []tpt.CapableConn{serverConn1, serverConn2} { go func(conn tpt.CapableConn) { - defer GinkgoRecover() - str, err := conn.AcceptStream() - Expect(err).ToNot(HaveOccurred()) - str.CloseWrite() - d, err := ioutil.ReadAll(str) - Expect(err).ToNot(HaveOccurred()) - Expect(d).To(Equal(data)) - done <- struct{}{} + str, err := conn.OpenStream(context.Background()) + require.NoError(t, err) + defer str.Close() + _, err = str.Write(data) + require.NoError(t, err) }(c) } + }() + + clientTransport, err := NewTransport(clientKey, nil, nil) + require.NoError(t, err) + defer clientTransport.(io.Closer).Close() + c1, err := clientTransport.Dial(context.Background(), ln1.Multiaddr(), serverID) + require.NoError(t, err) + defer c1.Close() + c2, err := clientTransport.Dial(context.Background(), ln2.Multiaddr(), serverID2) + require.NoError(t, err) + defer c2.Close() + + done := make(chan struct{}, 2) + // receive the data on both connections at the same time + for _, c := range []tpt.CapableConn{c1, c2} { + go func(conn tpt.CapableConn) { + str, err := conn.AcceptStream() + require.NoError(t, err) + str.CloseWrite() + d, err := ioutil.ReadAll(str) + require.NoError(t, err) + require.Equal(t, d, data) + done <- struct{}{} + }(c) + } - Eventually(done, 15*time.Second).Should(Receive()) - Eventually(done, 15*time.Second).Should(Receive()) - }) + for i := 0; i < 2; i++ { + require.Eventually(t, func() bool { + select { + case <-done: + return true + default: + return false + } + }, 15*time.Second, 50*time.Millisecond) + } +} - It("sends stateless resets", func() { - serverTransport, err := NewTransport(serverKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) - defer serverTransport.(io.Closer).Close() - ln := runServer(serverTransport, "/ip4/127.0.0.1/udp/0/quic") - - var drop uint32 - serverPort := ln.Addr().(*net.UDPAddr).Port - proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{ - RemoteAddr: fmt.Sprintf("localhost:%d", serverPort), - DropPacket: func(quicproxy.Direction, []byte) bool { - return atomic.LoadUint32(&drop) > 0 - }, - }) - Expect(err).ToNot(HaveOccurred()) - defer proxy.Close() - - // establish a connection - clientTransport, err := NewTransport(clientKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) - defer clientTransport.(io.Closer).Close() - proxyAddr, err := toQuicMultiaddr(proxy.LocalAddr()) - Expect(err).ToNot(HaveOccurred()) - conn, err := clientTransport.Dial(context.Background(), proxyAddr, serverID) - Expect(err).ToNot(HaveOccurred()) - go func() { - defer GinkgoRecover() - conn, err := ln.Accept() - Expect(err).ToNot(HaveOccurred()) - str, err := conn.OpenStream(context.Background()) - Expect(err).ToNot(HaveOccurred()) - str.Write([]byte("foobar")) - }() +func TestStatelessReset(t *testing.T) { + origGarbageCollectInterval := garbageCollectInterval + origMaxUnusedDuration := maxUnusedDuration - str, err := conn.AcceptStream() - Expect(err).ToNot(HaveOccurred()) - _, err = str.Read(make([]byte, 6)) - Expect(err).ToNot(HaveOccurred()) - - // Stop forwarding packets and close the server. - // This prevents the CONNECTION_CLOSE from reaching the client. - atomic.StoreUint32(&drop, 1) - Expect(ln.Close()).To(Succeed()) - time.Sleep(100 * time.Millisecond) // give the kernel some time to free the UDP port - ln = runServer(serverTransport, fmt.Sprintf("/ip4/127.0.0.1/udp/%d/quic", serverPort)) - defer ln.Close() - // Now that the new server is up, re-enable packet forwarding. - atomic.StoreUint32(&drop, 0) - - // Trigger something (not too small) to be sent, so that we receive the stateless reset. - // The new server doesn't have any state for the previously established connection. - // We expect it to send a stateless reset. - _, rerr := str.Write([]byte("Lorem ipsum dolor sit amet.")) - if rerr == nil { - _, rerr = str.Read([]byte{0, 0}) - } - Expect(rerr).To(HaveOccurred()) - Expect(rerr.Error()).To(ContainSubstring("received a stateless reset")) - }) + garbageCollectInterval = 50 * time.Millisecond + maxUnusedDuration = 0 - It("hole punches", func() { - t1, err := NewTransport(serverKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) - defer t1.(io.Closer).Close() - laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") - Expect(err).ToNot(HaveOccurred()) - ln1, err := t1.Listen(laddr) - Expect(err).ToNot(HaveOccurred()) - done1 := make(chan struct{}) - go func() { - defer GinkgoRecover() - defer close(done1) - if _, err := ln1.Accept(); err == nil { - Fail("didn't expect to accept any connections") - } - }() + t.Cleanup(func() { + garbageCollectInterval = origGarbageCollectInterval + maxUnusedDuration = origMaxUnusedDuration + }) - t2, err := NewTransport(clientKey, nil, nil) - Expect(err).ToNot(HaveOccurred()) - defer t2.(io.Closer).Close() - ln2, err := t2.Listen(laddr) - Expect(err).ToNot(HaveOccurred()) - done2 := make(chan struct{}) - go func() { - defer GinkgoRecover() - defer close(done2) - if _, err := ln2.Accept(); err == nil { - Fail("didn't expect to accept any connections") - } - }() - connChan := make(chan tpt.CapableConn) - go func() { - defer GinkgoRecover() - conn, err := t2.Dial( - n.WithSimultaneousConnect(context.Background(), false, ""), - ln1.Multiaddr(), - serverID, - ) - Expect(err).ToNot(HaveOccurred()) - connChan <- conn - }() - conn1, err := t1.Dial( - n.WithSimultaneousConnect(context.Background(), true, ""), - ln2.Multiaddr(), - clientID, - ) - Expect(err).ToNot(HaveOccurred()) - defer conn1.Close() - Expect(conn1.RemotePeer()).To(Equal(clientID)) - var conn2 tpt.CapableConn - Eventually(connChan).Should(Receive(&conn2)) - defer conn2.Close() - Expect(conn2.RemotePeer()).To(Equal(serverID)) - ln1.Close() - ln2.Close() - Eventually(done1).Should(BeClosed()) - Eventually(done2).Should(BeClosed()) + serverID, serverKey := createPeer(t) + _, clientKey := createPeer(t) + + serverTransport, err := NewTransport(serverKey, nil, nil) + require.NoError(t, err) + defer serverTransport.(io.Closer).Close() + ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic") + + var drop uint32 + serverPort := ln.Addr().(*net.UDPAddr).Port + proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{ + RemoteAddr: fmt.Sprintf("localhost:%d", serverPort), + DropPacket: func(quicproxy.Direction, []byte) bool { + return atomic.LoadUint32(&drop) > 0 + }, }) -}) + require.NoError(t, err) + defer proxy.Close() + + // establish a connection + clientTransport, err := NewTransport(clientKey, nil, nil) + require.NoError(t, err) + defer clientTransport.(io.Closer).Close() + proxyAddr, err := toQuicMultiaddr(proxy.LocalAddr()) + require.NoError(t, err) + conn, err := clientTransport.Dial(context.Background(), proxyAddr, serverID) + require.NoError(t, err) + go func() { + conn, err := ln.Accept() + require.NoError(t, err) + str, err := conn.OpenStream(context.Background()) + require.NoError(t, err) + str.Write([]byte("foobar")) + }() + + str, err := conn.AcceptStream() + require.NoError(t, err) + _, err = str.Read(make([]byte, 6)) + require.NoError(t, err) + + // Stop forwarding packets and close the server. + // This prevents the CONNECTION_CLOSE from reaching the client. + atomic.StoreUint32(&drop, 1) + ln.Close() + // require.NoError(t, ln.Close()) + time.Sleep(2000 * time.Millisecond) // give the kernel some time to free the UDP port + ln = runServer(t, serverTransport, fmt.Sprintf("/ip4/127.0.0.1/udp/%d/quic", serverPort)) + defer ln.Close() + // Now that the new server is up, re-enable packet forwarding. + atomic.StoreUint32(&drop, 0) + + // Trigger something (not too small) to be sent, so that we receive the stateless reset. + // The new server doesn't have any state for the previously established connection. + // We expect it to send a stateless reset. + _, rerr := str.Write([]byte("Lorem ipsum dolor sit amet.")) + if rerr == nil { + _, rerr = str.Read([]byte{0, 0}) + } + require.Error(t, rerr) + require.Contains(t, rerr.Error(), "received a stateless reset") +} + +func TestHolePunching(t *testing.T) { + serverID, serverKey := createPeer(t) + clientID, clientKey := createPeer(t) + + t1, err := NewTransport(serverKey, nil, nil) + require.NoError(t, err) + defer t1.(io.Closer).Close() + laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + require.NoError(t, err) + ln1, err := t1.Listen(laddr) + require.NoError(t, err) + done1 := make(chan struct{}) + go func() { + defer close(done1) + _, err := ln1.Accept() + require.Error(t, err, "didn't expect to accept any connections") + }() + + t2, err := NewTransport(clientKey, nil, nil) + require.NoError(t, err) + defer t2.(io.Closer).Close() + ln2, err := t2.Listen(laddr) + require.NoError(t, err) + done2 := make(chan struct{}) + go func() { + defer close(done2) + _, err := ln2.Accept() + require.Error(t, err, "didn't expect to accept any connections") + }() + connChan := make(chan tpt.CapableConn) + go func() { + conn, err := t2.Dial( + n.WithSimultaneousConnect(context.Background(), false, ""), + ln1.Multiaddr(), + serverID, + ) + require.NoError(t, err) + connChan <- conn + }() + conn1, err := t1.Dial( + n.WithSimultaneousConnect(context.Background(), true, ""), + ln2.Multiaddr(), + clientID, + ) + require.NoError(t, err) + defer conn1.Close() + require.Equal(t, conn1.RemotePeer(), clientID) + var conn2 tpt.CapableConn + require.Eventually(t, func() bool { + select { + case conn2 = <-connChan: + return true + default: + return false + } + }, 100*time.Millisecond, 10*time.Millisecond) + defer conn2.Close() + require.Equal(t, conn2.RemotePeer(), serverID) + ln1.Close() + ln2.Close() + <-done1 + <-done2 +} diff --git a/p2p/transport/quic/libp2pquic_suite_test.go b/p2p/transport/quic/libp2pquic_suite_test.go index 8bef2305b1..5144ae396f 100644 --- a/p2p/transport/quic/libp2pquic_suite_test.go +++ b/p2p/transport/quic/libp2pquic_suite_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/golang/mock/gomock" "github.com/lucas-clemente/quic-go" . "github.com/onsi/ginkgo" @@ -25,12 +24,9 @@ var ( garbageCollectIntervalOrig time.Duration maxUnusedDurationOrig time.Duration origQuicConfig *quic.Config - mockCtrl *gomock.Controller ) var _ = BeforeEach(func() { - mockCtrl = gomock.NewController(GinkgoT()) - garbageCollectIntervalOrig = garbageCollectInterval maxUnusedDurationOrig = maxUnusedDuration garbageCollectInterval = 50 * time.Millisecond @@ -40,8 +36,6 @@ var _ = BeforeEach(func() { }) var _ = AfterEach(func() { - mockCtrl.Finish() - garbageCollectInterval = garbageCollectIntervalOrig maxUnusedDuration = maxUnusedDurationOrig quicConfig = origQuicConfig From 591c57c59e2d99993d03c3a6ad8abf29180b5748 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 3 Jan 2022 17:57:23 +0400 Subject: [PATCH 127/138] use the quic.OOBCapablePacketConn in tests --- p2p/transport/quic/listener_test.go | 10 +--------- p2p/transport/quic/transport_test.go | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index f8d2041f38..3958778978 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "net" - "syscall" ic "github.com/libp2p/go-libp2p-core/crypto" tpt "github.com/libp2p/go-libp2p-core/transport" @@ -20,13 +19,6 @@ import ( . "github.com/onsi/gomega" ) -// interface containing some methods defined on the net.UDPConn, but not the net.PacketConn -type udpConn interface { - ReadFromUDP(b []byte) (int, *net.UDPAddr, error) - SetReadBuffer(bytes int) error - SyscallConn() (syscall.RawConn, error) -} - var _ = Describe("Listener", func() { var t tpt.Transport @@ -58,7 +50,7 @@ var _ = Describe("Listener", func() { Expect(err).To(MatchError("listen error")) Expect(conn).ToNot(BeNil()) defer conn.Close() - _, ok := conn.(udpConn) + _, ok := conn.(quic.OOBCapablePacketConn) Expect(ok).To(BeTrue()) }) diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go index 6ea09b6dc5..72eb0c49e9 100644 --- a/p2p/transport/quic/transport_test.go +++ b/p2p/transport/quic/transport_test.go @@ -93,7 +93,7 @@ func TestConnectionPassedToQUIC(t *testing.T) { require.EqualError(t, err, "listen error") require.NotNil(t, conn) defer conn.Close() - if _, ok := conn.(udpConn); !ok { + if _, ok := conn.(quic.OOBCapablePacketConn); !ok { t.Fatal("connection passed to quic-go cannot be type asserted to a *net.UDPConn") } } From 4b48046ab0a6ad913791e06c3f7a8e6d5f206735 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 3 Jan 2022 18:12:40 +0400 Subject: [PATCH 128/138] migrate the listener tests away from Ginkgo --- p2p/transport/quic/listener_test.go | 191 +++++++++++++--------------- 1 file changed, 87 insertions(+), 104 deletions(-) diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index 3958778978..4de9da3ed1 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -9,123 +9,106 @@ import ( "fmt" "io" "net" + "testing" + "time" ic "github.com/libp2p/go-libp2p-core/crypto" tpt "github.com/libp2p/go-libp2p-core/transport" - "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "github.com/stretchr/testify/require" ) -var _ = Describe("Listener", func() { - var t tpt.Transport - - BeforeEach(func() { - rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) - Expect(err).ToNot(HaveOccurred()) - key, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(rsaKey)) - Expect(err).ToNot(HaveOccurred()) - t, err = NewTransport(key, nil, nil) - Expect(err).ToNot(HaveOccurred()) - }) - - AfterEach(func() { - Expect(t.(io.Closer).Close()).To(Succeed()) - }) +func newTransport(t *testing.T) tpt.Transport { + rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + key, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(rsaKey)) + require.NoError(t, err) + tr, err := NewTransport(key, nil, nil) + require.NoError(t, err) + return tr +} - It("uses a conn that can interface assert to a UDPConn for listening", func() { - origQuicListen := quicListen - defer func() { quicListen = origQuicListen }() +// The conn passed to quic-go should be a conn that quic-go can be +// type-asserted to a UDPConn. That way, it can use all kinds of optimizations. +func TestConnUsedForListening(t *testing.T) { + origQuicListen := quicListen + t.Cleanup(func() { quicListen = origQuicListen }) - var conn net.PacketConn - quicListen = func(c net.PacketConn, _ *tls.Config, _ *quic.Config) (quic.Listener, error) { - conn = c - return nil, errors.New("listen error") - } - localAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") - Expect(err).ToNot(HaveOccurred()) - _, err = t.Listen(localAddr) - Expect(err).To(MatchError("listen error")) - Expect(conn).ToNot(BeNil()) - defer conn.Close() - _, ok := conn.(quic.OOBCapablePacketConn) - Expect(ok).To(BeTrue()) - }) + var conn net.PacketConn + quicListen = func(c net.PacketConn, _ *tls.Config, _ *quic.Config) (quic.Listener, error) { + conn = c + return nil, errors.New("listen error") + } + localAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") + require.NoError(t, err) - Context("listening on the right address", func() { - It("returns the address it is listening on", func() { - localAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") - Expect(err).ToNot(HaveOccurred()) - ln, err := t.Listen(localAddr) - Expect(err).ToNot(HaveOccurred()) - defer ln.Close() - netAddr := ln.Addr() - Expect(netAddr).To(BeAssignableToTypeOf(&net.UDPAddr{})) - port := netAddr.(*net.UDPAddr).Port - Expect(port).ToNot(BeZero()) - Expect(ln.Multiaddr().String()).To(Equal(fmt.Sprintf("/ip4/127.0.0.1/udp/%d/quic", port))) - }) + tr := newTransport(t) + defer tr.(io.Closer).Close() + _, err = tr.Listen(localAddr) + require.EqualError(t, err, "listen error") + require.NotNil(t, conn) + defer conn.Close() + _, ok := conn.(quic.OOBCapablePacketConn) + require.True(t, ok) +} - It("returns the address it is listening on, for listening on IPv4", func() { - localAddr, err := ma.NewMultiaddr("/ip4/0.0.0.0/udp/0/quic") - Expect(err).ToNot(HaveOccurred()) - ln, err := t.Listen(localAddr) - Expect(err).ToNot(HaveOccurred()) - defer ln.Close() - netAddr := ln.Addr() - Expect(netAddr).To(BeAssignableToTypeOf(&net.UDPAddr{})) - port := netAddr.(*net.UDPAddr).Port - Expect(port).ToNot(BeZero()) - Expect(ln.Multiaddr().String()).To(Equal(fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic", port))) - }) +func TestListenAddr(t *testing.T) { + tr := newTransport(t) + defer tr.(io.Closer).Close() - It("returns the address it is listening on, for listening on IPv6", func() { - localAddr, err := ma.NewMultiaddr("/ip6/::/udp/0/quic") - Expect(err).ToNot(HaveOccurred()) - ln, err := t.Listen(localAddr) - Expect(err).ToNot(HaveOccurred()) - defer ln.Close() - netAddr := ln.Addr() - Expect(netAddr).To(BeAssignableToTypeOf(&net.UDPAddr{})) - port := netAddr.(*net.UDPAddr).Port - Expect(port).ToNot(BeZero()) - Expect(ln.Multiaddr().String()).To(Equal(fmt.Sprintf("/ip6/::/udp/%d/quic", port))) - }) + t.Run("for IPv4", func(t *testing.T) { + localAddr := ma.StringCast("/ip4/127.0.0.1/udp/0/quic") + ln, err := tr.Listen(localAddr) + require.NoError(t, err) + defer ln.Close() + port := ln.Addr().(*net.UDPAddr).Port + require.NotZero(t, port) + require.Equal(t, ln.Multiaddr().String(), fmt.Sprintf("/ip4/127.0.0.1/udp/%d/quic", port)) }) - Context("accepting connections", func() { - var localAddr ma.Multiaddr - - BeforeEach(func() { - var err error - localAddr, err = ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") - Expect(err).ToNot(HaveOccurred()) - }) + t.Run("for IPv6", func(t *testing.T) { + localAddr := ma.StringCast("/ip6/::/udp/0/quic") + ln, err := tr.Listen(localAddr) + require.NoError(t, err) + defer ln.Close() + port := ln.Addr().(*net.UDPAddr).Port + require.NotZero(t, port) + require.Equal(t, ln.Multiaddr().String(), fmt.Sprintf("/ip6/::/udp/%d/quic", port)) + }) +} - It("returns Accept when it is closed", func() { - addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") - Expect(err).ToNot(HaveOccurred()) - ln, err := t.Listen(addr) - Expect(err).ToNot(HaveOccurred()) - done := make(chan struct{}) - go func() { - defer GinkgoRecover() - ln.Accept() - close(done) - }() - Consistently(done).ShouldNot(BeClosed()) - Expect(ln.Close()).To(Succeed()) - Eventually(done).Should(BeClosed()) - }) +func TestAccepting(t *testing.T) { + tr := newTransport(t) + defer tr.(io.Closer).Close() + ln, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic")) + require.NoError(t, err) + done := make(chan struct{}) + go func() { + ln.Accept() + close(done) + }() + time.Sleep(100 * time.Millisecond) + select { + case <-done: + t.Fatal("Accept didn't block") + default: + } + require.NoError(t, ln.Close()) + select { + case <-done: + case <-time.After(100 * time.Millisecond): + t.Fatal("Accept didn't return after the listener was closed") + } +} - It("doesn't accept Accept calls after it is closed", func() { - ln, err := t.Listen(localAddr) - Expect(err).ToNot(HaveOccurred()) - Expect(ln.Close()).To(Succeed()) - _, err = ln.Accept() - Expect(err).To(HaveOccurred()) - }) - }) -}) +func TestAcceptAfterClose(t *testing.T) { + tr := newTransport(t) + defer tr.(io.Closer).Close() + ln, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic")) + require.NoError(t, err) + require.NoError(t, ln.Close()) + _, err = ln.Accept() + require.Error(t, err) +} From 415b07df911b5c4f98e5ea133f88cf512e05e738 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 3 Jan 2022 18:30:29 +0400 Subject: [PATCH 129/138] migrate the multiaddr tests away from Ginkgo --- p2p/transport/quic/quic_multiaddr_test.go | 39 +++++++++++------------ 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/p2p/transport/quic/quic_multiaddr_test.go b/p2p/transport/quic/quic_multiaddr_test.go index 48949d3aa1..db7cdb34cd 100644 --- a/p2p/transport/quic/quic_multiaddr_test.go +++ b/p2p/transport/quic/quic_multiaddr_test.go @@ -2,29 +2,26 @@ package libp2pquic import ( "net" + "testing" ma "github.com/multiformats/go-multiaddr" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "github.com/stretchr/testify/require" ) -var _ = Describe("QUIC Multiaddr", func() { - It("converts a net.Addr to a QUIC Multiaddr", func() { - addr := &net.UDPAddr{IP: net.IPv4(192, 168, 0, 42), Port: 1337} - maddr, err := toQuicMultiaddr(addr) - Expect(err).ToNot(HaveOccurred()) - Expect(maddr.String()).To(Equal("/ip4/192.168.0.42/udp/1337/quic")) - }) +func TestConvertToQuicMultiaddr(t *testing.T) { + addr := &net.UDPAddr{IP: net.IPv4(192, 168, 0, 42), Port: 1337} + maddr, err := toQuicMultiaddr(addr) + require.NoError(t, err) + require.Equal(t, maddr.String(), "/ip4/192.168.0.42/udp/1337/quic") +} - It("converts a QUIC Multiaddr to a net.Addr", func() { - maddr, err := ma.NewMultiaddr("/ip4/192.168.0.42/udp/1337/quic") - Expect(err).ToNot(HaveOccurred()) - addr, err := fromQuicMultiaddr(maddr) - Expect(err).ToNot(HaveOccurred()) - Expect(addr).To(BeAssignableToTypeOf(&net.UDPAddr{})) - udpAddr := addr.(*net.UDPAddr) - Expect(udpAddr.IP).To(Equal(net.IPv4(192, 168, 0, 42))) - Expect(udpAddr.Port).To(Equal(1337)) - }) -}) +func TestConvertFromQuicMultiaddr(t *testing.T) { + maddr, err := ma.NewMultiaddr("/ip4/192.168.0.42/udp/1337/quic") + require.NoError(t, err) + addr, err := fromQuicMultiaddr(maddr) + require.NoError(t, err) + udpAddr, ok := addr.(*net.UDPAddr) + require.True(t, ok) + require.Equal(t, udpAddr.IP, net.IPv4(192, 168, 0, 42)) + require.Equal(t, udpAddr.Port, 1337) +} From 6a0fe7559c5faee0f1e2d32d4cad7f749633ce4f Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 3 Jan 2022 18:57:22 +0400 Subject: [PATCH 130/138] migrate the reuse tests away from Ginkgo --- p2p/transport/quic/reuse_test.go | 233 ++++++++++++++++--------------- 1 file changed, 124 insertions(+), 109 deletions(-) diff --git a/p2p/transport/quic/reuse_test.go b/p2p/transport/quic/reuse_test.go index b43d23d498..46fb821ebc 100644 --- a/p2p/transport/quic/reuse_test.go +++ b/p2p/transport/quic/reuse_test.go @@ -5,11 +5,11 @@ import ( "net" "runtime/pprof" "strings" + "testing" "time" "github.com/libp2p/go-netroute" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "github.com/stretchr/testify/require" ) func (c *reuseConn) GetCount() int { @@ -35,124 +35,139 @@ func closeAllConns(reuse *reuse) { reuse.mutex.Unlock() } -func OnPlatformsWithRoutingTablesIt(description string, f interface{}) { - if _, err := netroute.New(); err == nil { - It(description, f) - } else { - PIt(description, f) - } +func platformHasRoutingTables() bool { + _, err := netroute.New() + return err == nil } -var _ = Describe("Reuse", func() { - var reuse *reuse +func isGarbageCollectorRunning() bool { + var b bytes.Buffer + pprof.Lookup("goroutine").WriteTo(&b, 1) + return strings.Contains(b.String(), "go-libp2p-quic-transport.(*reuse).gc") +} - BeforeEach(func() { - reuse = newReuse() +func cleanup(t *testing.T, reuse *reuse) { + t.Cleanup(func() { + closeAllConns(reuse) + reuse.Close() + require.False(t, isGarbageCollectorRunning(), "reuse gc still running") }) +} - AfterEach(func() { - isGarbageCollectorRunning := func() bool { - var b bytes.Buffer - pprof.Lookup("goroutine").WriteTo(&b, 1) - return strings.Contains(b.String(), "go-libp2p-quic-transport.(*reuse).gc") - } +func TestReuseListenOnAllIPv4(t *testing.T) { + reuse := newReuse() + cleanup(t, reuse) - Expect(reuse.Close()).To(Succeed()) - Expect(isGarbageCollectorRunning()).To(BeFalse()) - }) + addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") + require.NoError(t, err) + conn, err := reuse.Listen("udp4", addr) + require.NoError(t, err) + require.Equal(t, conn.GetCount(), 1) +} + +func TestReuseListenOnAllIPv6(t *testing.T) { + reuse := newReuse() + cleanup(t, reuse) + + addr, err := net.ResolveUDPAddr("udp6", "[::]:1234") + require.NoError(t, err) + conn, err := reuse.Listen("udp6", addr) + require.NoError(t, err) + require.Equal(t, conn.GetCount(), 1) +} + +func TestReuseCreateNewGlobalConnOnDial(t *testing.T) { + reuse := newReuse() + cleanup(t, reuse) + + addr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") + require.NoError(t, err) + conn, err := reuse.Dial("udp4", addr) + require.NoError(t, err) + require.Equal(t, conn.GetCount(), 1) + laddr := conn.LocalAddr().(*net.UDPAddr) + require.Equal(t, laddr.IP.String(), "0.0.0.0") + require.NotEqual(t, laddr.Port, 0) +} - Context("creating and reusing connections", func() { - AfterEach(func() { closeAllConns(reuse) }) - - It("creates a new global connection when listening on 0.0.0.0", func() { - addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") - Expect(err).ToNot(HaveOccurred()) - conn, err := reuse.Listen("udp4", addr) - Expect(err).ToNot(HaveOccurred()) - Expect(conn.GetCount()).To(Equal(1)) - }) - - It("creates a new global connection when listening on [::]", func() { - addr, err := net.ResolveUDPAddr("udp6", "[::]:1234") - Expect(err).ToNot(HaveOccurred()) - conn, err := reuse.Listen("udp6", addr) - Expect(err).ToNot(HaveOccurred()) - Expect(conn.GetCount()).To(Equal(1)) - }) - - It("creates a new global connection when dialing", func() { - addr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") - Expect(err).ToNot(HaveOccurred()) - conn, err := reuse.Dial("udp4", addr) - Expect(err).ToNot(HaveOccurred()) - Expect(conn.GetCount()).To(Equal(1)) - laddr := conn.LocalAddr().(*net.UDPAddr) - Expect(laddr.IP.String()).To(Equal("0.0.0.0")) - Expect(laddr.Port).ToNot(BeZero()) - }) - - It("reuses a connection it created for listening when dialing", func() { - // listen - addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") - Expect(err).ToNot(HaveOccurred()) - lconn, err := reuse.Listen("udp4", addr) - Expect(err).ToNot(HaveOccurred()) - Expect(lconn.GetCount()).To(Equal(1)) - // dial - raddr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") - Expect(err).ToNot(HaveOccurred()) - conn, err := reuse.Dial("udp4", raddr) - Expect(err).ToNot(HaveOccurred()) - Expect(conn.GetCount()).To(Equal(2)) - }) - - OnPlatformsWithRoutingTablesIt("reuses a connection it created for listening on a specific interface", func() { - router, err := netroute.New() - Expect(err).ToNot(HaveOccurred()) - - raddr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") - Expect(err).ToNot(HaveOccurred()) - _, _, ip, err := router.Route(raddr.IP) - Expect(err).ToNot(HaveOccurred()) - // listen - addr, err := net.ResolveUDPAddr("udp4", ip.String()+":0") - Expect(err).ToNot(HaveOccurred()) - lconn, err := reuse.Listen("udp4", addr) - Expect(err).ToNot(HaveOccurred()) - Expect(lconn.GetCount()).To(Equal(1)) - // dial - conn, err := reuse.Dial("udp4", raddr) - Expect(err).ToNot(HaveOccurred()) - Expect(conn.GetCount()).To(Equal(2)) - }) +func TestReuseConnectionWhenDialing(t *testing.T) { + reuse := newReuse() + cleanup(t, reuse) + + addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") + require.NoError(t, err) + lconn, err := reuse.Listen("udp4", addr) + require.NoError(t, err) + require.Equal(t, lconn.GetCount(), 1) + // dial + raddr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") + require.NoError(t, err) + conn, err := reuse.Dial("udp4", raddr) + require.NoError(t, err) + require.Equal(t, conn.GetCount(), 2) +} + +func TestReuseListenOnSpecificInterface(t *testing.T) { + if platformHasRoutingTables() { + t.Skip("this test only works on platforms that support routing tables") + } + reuse := newReuse() + cleanup(t, reuse) + + router, err := netroute.New() + require.NoError(t, err) + + raddr, err := net.ResolveUDPAddr("udp4", "1.1.1.1:1234") + require.NoError(t, err) + _, _, ip, err := router.Route(raddr.IP) + require.NoError(t, err) + // listen + addr, err := net.ResolveUDPAddr("udp4", ip.String()+":0") + require.NoError(t, err) + lconn, err := reuse.Listen("udp4", addr) + require.NoError(t, err) + require.Equal(t, lconn.GetCount(), 1) + // dial + conn, err := reuse.Dial("udp4", raddr) + require.NoError(t, err) + require.Equal(t, conn.GetCount(), 1) +} + +func TestReuseGarbageCollect(t *testing.T) { + maxUnusedDurationOrig := maxUnusedDuration + garbageCollectIntervalOrig := garbageCollectInterval + t.Cleanup(func() { + maxUnusedDuration = maxUnusedDurationOrig + garbageCollectInterval = garbageCollectIntervalOrig }) + garbageCollectInterval = 50 * time.Millisecond + maxUnusedDuration = 100 * time.Millisecond - It("garbage collects connections once they're not used any more for a certain time", func() { - numGlobals := func() int { - reuse.mutex.Lock() - defer reuse.mutex.Unlock() - return len(reuse.global) - } + reuse := newReuse() + cleanup(t, reuse) - maxUnusedDuration = 100 * time.Millisecond + numGlobals := func() int { + reuse.mutex.Lock() + defer reuse.mutex.Unlock() + return len(reuse.global) + } - addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") - Expect(err).ToNot(HaveOccurred()) - lconn, err := reuse.Listen("udp4", addr) - Expect(err).ToNot(HaveOccurred()) - Expect(lconn.GetCount()).To(Equal(1)) + addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") + require.NoError(t, err) + lconn, err := reuse.Listen("udp4", addr) + require.NoError(t, err) + require.Equal(t, lconn.GetCount(), 1) - closeTime := time.Now() - lconn.DecreaseCount() + closeTime := time.Now() + lconn.DecreaseCount() - for { - num := numGlobals() - if closeTime.Add(maxUnusedDuration).Before(time.Now()) { - break - } - Expect(num).To(Equal(1)) - time.Sleep(2 * time.Millisecond) + for { + num := numGlobals() + if closeTime.Add(maxUnusedDuration).Before(time.Now()) { + break } - Eventually(numGlobals).Should(BeZero()) - }) -}) + require.Equal(t, num, 1) + time.Sleep(2 * time.Millisecond) + } + require.Eventually(t, func() bool { return numGlobals() == 0 }, 100*time.Millisecond, 5*time.Millisecond) +} From 2a41315ba14aac3c64697812c45d025a9b9d1d71 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 3 Jan 2022 19:07:13 +0400 Subject: [PATCH 131/138] migrate the tracer tests away from Ginkgo --- p2p/transport/quic/tracer_test.go | 124 ++++++++++++++---------------- 1 file changed, 58 insertions(+), 66 deletions(-) diff --git a/p2p/transport/quic/tracer_test.go b/p2p/transport/quic/tracer_test.go index 145a3261c6..2f6d582c1b 100644 --- a/p2p/transport/quic/tracer_test.go +++ b/p2p/transport/quic/tracer_test.go @@ -2,81 +2,73 @@ package libp2pquic import ( "bytes" - "fmt" "io/ioutil" "os" + "strings" + "testing" + + "github.com/stretchr/testify/require" "github.com/klauspost/compress/zstd" "github.com/lucas-clemente/quic-go/logging" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" ) -var _ = Describe("qlogger", func() { - var qlogDir string - - BeforeEach(func() { - var err error - qlogDir, err = ioutil.TempDir("", "libp2p-quic-transport-test") - Expect(err).ToNot(HaveOccurred()) - fmt.Fprintf(GinkgoWriter, "Creating temporary directory: %s\n", qlogDir) - initQlogger(qlogDir) - }) - - AfterEach(func() { - Expect(os.RemoveAll(qlogDir)).To(Succeed()) - }) +func createLogDir(t *testing.T) string { + dir, err := ioutil.TempDir("", "libp2p-quic-transport-test") + require.NoError(t, err) + t.Cleanup(func() { os.RemoveAll(dir) }) + return dir +} - getFile := func() os.FileInfo { - files, err := ioutil.ReadDir(qlogDir) - Expect(err).ToNot(HaveOccurred()) - Expect(files).To(HaveLen(1)) - return files[0] - } +func getFile(t *testing.T, dir string) os.FileInfo { + files, err := ioutil.ReadDir(dir) + require.NoError(t, err) + require.Len(t, files, 1) + return files[0] +} - It("saves a qlog", func() { - logger := newQlogger(qlogDir, logging.PerspectiveServer, []byte{0xde, 0xad, 0xbe, 0xef}) - file := getFile() - Expect(string(file.Name()[0])).To(Equal(".")) - Expect(file.Name()).To(HaveSuffix(".qlog.swp")) - // close the logger. This should move the file. - Expect(logger.Close()).To(Succeed()) - file = getFile() - Expect(string(file.Name()[0])).ToNot(Equal(".")) - Expect(file.Name()).To(HaveSuffix(".qlog.zst")) - Expect(file.Name()).To(And( - ContainSubstring("server"), - ContainSubstring("deadbeef"), - )) - }) +func TestSaveQlog(t *testing.T) { + qlogDir := createLogDir(t) + logger := newQlogger(qlogDir, logging.PerspectiveServer, []byte{0xde, 0xad, 0xbe, 0xef}) + file := getFile(t, qlogDir) + require.Equal(t, string(file.Name()[0]), ".") + require.Truef(t, strings.HasSuffix(file.Name(), ".qlog.swp"), "expected %s to have the .qlog.swp file ending", file.Name()) + // close the logger. This should move the file. + require.NoError(t, logger.Close()) + file = getFile(t, qlogDir) + require.NotEqual(t, string(file.Name()[0]), ".") + require.Truef(t, strings.HasSuffix(file.Name(), ".qlog.zst"), "expected %s to have the .qlog.zst file ending", file.Name()) + require.Contains(t, file.Name(), "server") + require.Contains(t, file.Name(), "deadbeef") +} - It("buffers", func() { - logger := newQlogger(qlogDir, logging.PerspectiveServer, []byte("connid")) - initialSize := getFile().Size() - // Do a small write. - // Since the writter is buffered, this should not be written to disk yet. - logger.Write([]byte("foobar")) - Expect(getFile().Size()).To(Equal(initialSize)) - // Close the logger. This should flush the buffer to disk. - Expect(logger.Close()).To(Succeed()) - finalSize := getFile().Size() - fmt.Fprintf(GinkgoWriter, "initial log file size: %d, final log file size: %d\n", initialSize, finalSize) - Expect(finalSize).To(BeNumerically(">", initialSize)) - }) +func TestQlogBuffering(t *testing.T) { + qlogDir := createLogDir(t) + logger := newQlogger(qlogDir, logging.PerspectiveServer, []byte("connid")) + initialSize := getFile(t, qlogDir).Size() + // Do a small write. + // Since the writter is buffered, this should not be written to disk yet. + logger.Write([]byte("foobar")) + require.Equal(t, getFile(t, qlogDir).Size(), initialSize) + // Close the logger. This should flush the buffer to disk. + require.NoError(t, logger.Close()) + finalSize := getFile(t, qlogDir).Size() + t.Logf("initial log file size: %d, final log file size: %d\n", initialSize, finalSize) + require.Greater(t, finalSize, initialSize) +} - It("compresses", func() { - logger := newQlogger(qlogDir, logging.PerspectiveServer, []byte("connid")) - logger.Write([]byte("foobar")) - Expect(logger.Close()).To(Succeed()) - compressed, err := ioutil.ReadFile(qlogDir + "/" + getFile().Name()) - Expect(err).ToNot(HaveOccurred()) - Expect(compressed).ToNot(Equal("foobar")) - c, err := zstd.NewReader(bytes.NewReader(compressed)) - Expect(err).ToNot(HaveOccurred()) - data, err := ioutil.ReadAll(c) - Expect(err).ToNot(HaveOccurred()) - Expect(data).To(Equal([]byte("foobar"))) - }) -}) +func TestQlogCompression(t *testing.T) { + qlogDir := createLogDir(t) + logger := newQlogger(qlogDir, logging.PerspectiveServer, []byte("connid")) + logger.Write([]byte("foobar")) + require.NoError(t, logger.Close()) + compressed, err := ioutil.ReadFile(qlogDir + "/" + getFile(t, qlogDir).Name()) + require.NoError(t, err) + require.NotEqual(t, compressed, "foobar") + c, err := zstd.NewReader(bytes.NewReader(compressed)) + require.NoError(t, err) + data, err := ioutil.ReadAll(c) + require.NoError(t, err) + require.Equal(t, data, []byte("foobar")) +} From 078c78fb47b3315a2d9811b4e741d7502d1eaafb Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 3 Jan 2022 19:07:53 +0400 Subject: [PATCH 132/138] remove Ginkgo suite --- p2p/transport/quic/libp2pquic_suite_test.go | 42 --------------------- 1 file changed, 42 deletions(-) delete mode 100644 p2p/transport/quic/libp2pquic_suite_test.go diff --git a/p2p/transport/quic/libp2pquic_suite_test.go b/p2p/transport/quic/libp2pquic_suite_test.go deleted file mode 100644 index 5144ae396f..0000000000 --- a/p2p/transport/quic/libp2pquic_suite_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package libp2pquic - -import ( - mrand "math/rand" - "testing" - "time" - - "github.com/lucas-clemente/quic-go" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestLibp2pQuicTransport(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "libp2p QUIC Transport Suite") -} - -var _ = BeforeSuite(func() { - mrand.Seed(GinkgoRandomSeed()) -}) - -var ( - garbageCollectIntervalOrig time.Duration - maxUnusedDurationOrig time.Duration - origQuicConfig *quic.Config -) - -var _ = BeforeEach(func() { - garbageCollectIntervalOrig = garbageCollectInterval - maxUnusedDurationOrig = maxUnusedDuration - garbageCollectInterval = 50 * time.Millisecond - maxUnusedDuration = 0 - origQuicConfig = quicConfig - quicConfig = quicConfig.Clone() -}) - -var _ = AfterEach(func() { - garbageCollectInterval = garbageCollectIntervalOrig - maxUnusedDuration = maxUnusedDurationOrig - quicConfig = origQuicConfig -}) From 445a40e4de7bc1a74b4bef27cadbc584ba04a950 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 7 Jan 2022 17:36:15 +0400 Subject: [PATCH 133/138] don't start a Go routine for every connection dialed In the swarm, we're calling Close for every connection before it is garbage collected. We therefore don't need to start a Go routine here just to see when a connection is closed. We now also increment the reuse counter every time a connection is dialed. This simplifies closing the connection. --- p2p/transport/quic/conn.go | 7 ++++++- p2p/transport/quic/conn_test.go | 3 +++ p2p/transport/quic/listener.go | 5 ++--- p2p/transport/quic/transport.go | 5 +---- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index 4f74d25c6e..2950c09ab3 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -8,12 +8,13 @@ import ( "github.com/libp2p/go-libp2p-core/peer" tpt "github.com/libp2p/go-libp2p-core/transport" - quic "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go" ma "github.com/multiformats/go-multiaddr" ) type conn struct { sess quic.Session + pconn *reuseConn transport tpt.Transport localPeer peer.ID @@ -27,7 +28,11 @@ type conn struct { var _ tpt.CapableConn = &conn{} +// Close closes the connection. +// It must be called even if the peer closed the connection in order for +// garbage collection to properly work in this package. func (c *conn) Close() error { + c.pconn.DecreaseCount() return c.sess.CloseWithError(0, "") } diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 8c69b551da..6676153c13 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -353,12 +353,14 @@ func TestStatelessReset(t *testing.T) { require.NoError(t, err) conn, err := clientTransport.Dial(context.Background(), proxyAddr, serverID) require.NoError(t, err) + connChan := make(chan tpt.CapableConn) go func() { conn, err := ln.Accept() require.NoError(t, err) str, err := conn.OpenStream(context.Background()) require.NoError(t, err) str.Write([]byte("foobar")) + connChan <- conn }() str, err := conn.AcceptStream() @@ -370,6 +372,7 @@ func TestStatelessReset(t *testing.T) { // This prevents the CONNECTION_CLOSE from reaching the client. atomic.StoreUint32(&drop, 1) ln.Close() + (<-connChan).Close() // require.NoError(t, ln.Close()) time.Sleep(2000 * time.Millisecond) // give the kernel some time to free the UDP port ln = runServer(t, serverTransport, fmt.Sprintf("/ip4/127.0.0.1/udp/%d/quic", serverPort)) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 039f717c72..dfa63a0484 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -102,19 +102,18 @@ func (l *listener) setupConn(sess quic.Session) (*conn, error) { if err != nil { return nil, err } - remotePeerID, err := peer.IDFromPublicKey(remotePubKey) if err != nil { return nil, err } - remoteMultiaddr, err := toQuicMultiaddr(sess.RemoteAddr()) if err != nil { return nil, err } - + l.conn.IncreaseCount() return &conn{ sess: sess, + pconn: l.conn, transport: l.transport, localPeer: l.localPeer, localMultiaddr: l.localMultiaddr, diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 13efc95193..ee47213c9f 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -207,10 +207,6 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp pconn.DecreaseCount() return nil, errors.New("go-libp2p-quic-transport BUG: expected remote pub key to be set") } - go func() { - <-sess.Context().Done() - pconn.DecreaseCount() - }() localMultiaddr, err := toQuicMultiaddr(pconn.LocalAddr()) if err != nil { @@ -219,6 +215,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp } conn := &conn{ sess: sess, + pconn: pconn, transport: t, privKey: t.privKey, localPeer: t.localPeer, From a49983a58073933d997bfbd00cbffdea8438fb61 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 31 Dec 2021 13:43:13 +0400 Subject: [PATCH 134/138] use the Resource Manager --- p2p/transport/quic/cmd/client/main.go | 2 +- p2p/transport/quic/cmd/server/main.go | 2 +- p2p/transport/quic/conn.go | 15 ++- p2p/transport/quic/conn_test.go | 159 ++++++++++++++++++++++---- p2p/transport/quic/listener.go | 24 +++- p2p/transport/quic/listener_test.go | 13 ++- p2p/transport/quic/stream.go | 10 +- p2p/transport/quic/transport.go | 46 +++++--- p2p/transport/quic/transport_test.go | 2 +- 9 files changed, 215 insertions(+), 58 deletions(-) diff --git a/p2p/transport/quic/cmd/client/main.go b/p2p/transport/quic/cmd/client/main.go index c39ca8785e..50889cf1b6 100644 --- a/p2p/transport/quic/cmd/client/main.go +++ b/p2p/transport/quic/cmd/client/main.go @@ -38,7 +38,7 @@ func run(raddr string, p string) error { return err } - t, err := libp2pquic.NewTransport(priv, nil, nil) + t, err := libp2pquic.NewTransport(priv, nil, nil, nil) if err != nil { return err } diff --git a/p2p/transport/quic/cmd/server/main.go b/p2p/transport/quic/cmd/server/main.go index ec995bd0ff..cb5b528461 100644 --- a/p2p/transport/quic/cmd/server/main.go +++ b/p2p/transport/quic/cmd/server/main.go @@ -38,7 +38,7 @@ func run(port string) error { return err } - t, err := libp2pquic.NewTransport(priv, nil, nil) + t, err := libp2pquic.NewTransport(priv, nil, nil, nil) if err != nil { return err } diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index 2950c09ab3..d35ebf4d7b 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -4,7 +4,7 @@ import ( "context" ic "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/mux" + "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" tpt "github.com/libp2p/go-libp2p-core/transport" @@ -16,6 +16,7 @@ type conn struct { sess quic.Session pconn *reuseConn transport tpt.Transport + scope network.ConnManagementScope localPeer peer.ID privKey ic.PrivKey @@ -32,8 +33,10 @@ var _ tpt.CapableConn = &conn{} // It must be called even if the peer closed the connection in order for // garbage collection to properly work in this package. func (c *conn) Close() error { + err := c.sess.CloseWithError(0, "") c.pconn.DecreaseCount() - return c.sess.CloseWithError(0, "") + c.scope.Done() + return err } // IsClosed returns whether a connection is fully closed. @@ -42,13 +45,13 @@ func (c *conn) IsClosed() bool { } // OpenStream creates a new stream. -func (c *conn) OpenStream(ctx context.Context) (mux.MuxedStream, error) { +func (c *conn) OpenStream(ctx context.Context) (network.MuxedStream, error) { qstr, err := c.sess.OpenStreamSync(ctx) return &stream{Stream: qstr}, err } // AcceptStream accepts a stream opened by the other side. -func (c *conn) AcceptStream() (mux.MuxedStream, error) { +func (c *conn) AcceptStream() (network.MuxedStream, error) { qstr, err := c.sess.AcceptStream(context.Background()) return &stream{Stream: qstr}, err } @@ -86,3 +89,7 @@ func (c *conn) RemoteMultiaddr() ma.Multiaddr { func (c *conn) Transport() tpt.Transport { return c.transport } + +func (c *conn) Scope() network.ConnScope { + return c.scope +} diff --git a/p2p/transport/quic/conn_test.go b/p2p/transport/quic/conn_test.go index 6676153c13..2d789a956c 100644 --- a/p2p/transport/quic/conn_test.go +++ b/p2p/transport/quic/conn_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/rand" + "errors" "fmt" "io" "io/ioutil" @@ -14,9 +15,12 @@ import ( "time" ic "github.com/libp2p/go-libp2p-core/crypto" - n "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" tpt "github.com/libp2p/go-libp2p-core/transport" + + mocknetwork "github.com/libp2p/go-libp2p-testing/mocks/network" + quicproxy "github.com/lucas-clemente/quic-go/integrationtests/tools/proxy" ma "github.com/multiformats/go-multiaddr" @@ -46,11 +50,9 @@ func createPeer(t *testing.T) (peer.ID, ic.PrivKey) { return id, priv } -func runServer(t *testing.T, tr tpt.Transport, multiaddr string) tpt.Listener { +func runServer(t *testing.T, tr tpt.Transport, addr string) tpt.Listener { t.Helper() - addr, err := ma.NewMultiaddr(multiaddr) - require.NoError(t, err) - ln, err := tr.Listen(addr) + ln, err := tr.Listen(ma.StringCast(addr)) require.NoError(t, err) return ln } @@ -58,12 +60,12 @@ func runServer(t *testing.T, tr tpt.Transport, multiaddr string) tpt.Listener { func TestHandshake(t *testing.T) { serverID, serverKey := createPeer(t) clientID, clientKey := createPeer(t) - serverTransport, err := NewTransport(serverKey, nil, nil) + serverTransport, err := NewTransport(serverKey, nil, nil, nil) require.NoError(t, err) defer serverTransport.(io.Closer).Close() handshake := func(t *testing.T, ln tpt.Listener) { - clientTransport, err := NewTransport(clientKey, nil, nil) + clientTransport, err := NewTransport(clientKey, nil, nil, nil) require.NoError(t, err) defer clientTransport.(io.Closer).Close() conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) @@ -97,17 +99,126 @@ func TestHandshake(t *testing.T) { }) } +func TestResourceManagerSuccess(t *testing.T) { + serverID, serverKey := createPeer(t) + clientID, clientKey := createPeer(t) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + serverRcmgr := mocknetwork.NewMockResourceManager(ctrl) + serverTransport, err := NewTransport(serverKey, nil, nil, serverRcmgr) + require.NoError(t, err) + defer serverTransport.(io.Closer).Close() + ln, err := serverTransport.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic")) + require.NoError(t, err) + defer ln.Close() + + clientRcmgr := mocknetwork.NewMockResourceManager(ctrl) + clientTransport, err := NewTransport(clientKey, nil, nil, clientRcmgr) + require.NoError(t, err) + defer clientTransport.(io.Closer).Close() + + connChan := make(chan tpt.CapableConn) + serverConnScope := mocknetwork.NewMockConnManagementScope(ctrl) + go func() { + serverRcmgr.EXPECT().OpenConnection(network.DirInbound, false).Return(serverConnScope, nil) + serverConnScope.EXPECT().SetPeer(clientID) + serverConn, err := ln.Accept() + require.NoError(t, err) + connChan <- serverConn + }() + + connScope := mocknetwork.NewMockConnManagementScope(ctrl) + clientRcmgr.EXPECT().OpenConnection(network.DirOutbound, false).Return(connScope, nil) + connScope.EXPECT().SetPeer(serverID) + conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) + require.NoError(t, err) + serverConn := <-connChan + t.Log("received conn") + connScope.EXPECT().Done().MinTimes(1) // for dialed connections, we might call Done multiple times + conn.Close() + serverConnScope.EXPECT().Done() + serverConn.Close() +} + +func TestResourceManagerDialDenied(t *testing.T) { + _, clientKey := createPeer(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + rcmgr := mocknetwork.NewMockResourceManager(ctrl) + clientTransport, err := NewTransport(clientKey, nil, nil, rcmgr) + require.NoError(t, err) + defer clientTransport.(io.Closer).Close() + + connScope := mocknetwork.NewMockConnManagementScope(ctrl) + rcmgr.EXPECT().OpenConnection(network.DirOutbound, false).Return(connScope, nil) + rerr := errors.New("nope") + p := peer.ID("server") + connScope.EXPECT().SetPeer(p).Return(rerr) + connScope.EXPECT().Done() + + _, err = clientTransport.Dial(context.Background(), ma.StringCast("/ip4/127.0.0.1/udp/1234/quic"), p) + require.ErrorIs(t, err, rerr) +} + +func TestResourceManagerAcceptDenied(t *testing.T) { + serverID, serverKey := createPeer(t) + clientID, clientKey := createPeer(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + clientRcmgr := mocknetwork.NewMockResourceManager(ctrl) + clientTransport, err := NewTransport(clientKey, nil, nil, clientRcmgr) + require.NoError(t, err) + defer clientTransport.(io.Closer).Close() + + serverRcmgr := mocknetwork.NewMockResourceManager(ctrl) + serverConnScope := mocknetwork.NewMockConnManagementScope(ctrl) + rerr := errors.New("denied") + gomock.InOrder( + serverRcmgr.EXPECT().OpenConnection(network.DirInbound, false).Return(serverConnScope, nil), + serverConnScope.EXPECT().SetPeer(clientID).Return(rerr), + serverConnScope.EXPECT().Done(), + ) + serverTransport, err := NewTransport(serverKey, nil, nil, serverRcmgr) + require.NoError(t, err) + defer serverTransport.(io.Closer).Close() + ln, err := serverTransport.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic")) + require.NoError(t, err) + defer ln.Close() + connChan := make(chan tpt.CapableConn) + go func() { + ln.Accept() + close(connChan) + }() + + clientConnScope := mocknetwork.NewMockConnManagementScope(ctrl) + clientRcmgr.EXPECT().OpenConnection(network.DirOutbound, false).Return(clientConnScope, nil) + clientConnScope.EXPECT().SetPeer(serverID) + conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) + require.NoError(t, err) + _, err = conn.AcceptStream() + require.Error(t, err) + select { + case <-connChan: + t.Fatal("didn't expect to accept a connection") + default: + } +} + func TestStreams(t *testing.T) { serverID, serverKey := createPeer(t) _, clientKey := createPeer(t) - serverTransport, err := NewTransport(serverKey, nil, nil) + serverTransport, err := NewTransport(serverKey, nil, nil, nil) require.NoError(t, err) defer serverTransport.(io.Closer).Close() ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln.Close() - clientTransport, err := NewTransport(clientKey, nil, nil) + clientTransport, err := NewTransport(clientKey, nil, nil, nil) require.NoError(t, err) defer clientTransport.(io.Closer).Close() conn, err := clientTransport.Dial(context.Background(), ln.Multiaddr(), serverID) @@ -134,12 +245,12 @@ func TestHandshakeFailPeerIDMismatch(t *testing.T) { _, clientKey := createPeer(t) thirdPartyID, _ := createPeer(t) - serverTransport, err := NewTransport(serverKey, nil, nil) + serverTransport, err := NewTransport(serverKey, nil, nil, nil) require.NoError(t, err) defer serverTransport.(io.Closer).Close() ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic") - clientTransport, err := NewTransport(clientKey, nil, nil) + clientTransport, err := NewTransport(clientKey, nil, nil, nil) require.NoError(t, err) // dial, but expect the wrong peer ID _, err = clientTransport.Dial(context.Background(), ln.Multiaddr(), thirdPartyID) @@ -172,7 +283,7 @@ func TestConnectionGating(t *testing.T) { cg := NewMockConnectionGater(mockCtrl) t.Run("accepted connections", func(t *testing.T) { - serverTransport, err := NewTransport(serverKey, nil, cg) + serverTransport, err := NewTransport(serverKey, nil, cg, nil) defer serverTransport.(io.Closer).Close() require.NoError(t, err) ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic") @@ -187,7 +298,7 @@ func TestConnectionGating(t *testing.T) { require.NoError(t, err) }() - clientTransport, err := NewTransport(clientKey, nil, nil) + clientTransport, err := NewTransport(clientKey, nil, nil, nil) require.NoError(t, err) defer clientTransport.(io.Closer).Close() // make sure that connection attempts fails @@ -215,7 +326,7 @@ func TestConnectionGating(t *testing.T) { }) t.Run("secured connections", func(t *testing.T) { - serverTransport, err := NewTransport(serverKey, nil, nil) + serverTransport, err := NewTransport(serverKey, nil, nil, nil) require.NoError(t, err) defer serverTransport.(io.Closer).Close() ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic") @@ -224,7 +335,7 @@ func TestConnectionGating(t *testing.T) { cg := NewMockConnectionGater(mockCtrl) cg.EXPECT().InterceptSecured(gomock.Any(), gomock.Any(), gomock.Any()) - clientTransport, err := NewTransport(clientKey, nil, cg) + clientTransport, err := NewTransport(clientKey, nil, cg, nil) require.NoError(t, err) defer clientTransport.(io.Closer).Close() @@ -247,12 +358,12 @@ func TestDialTwo(t *testing.T) { _, clientKey := createPeer(t) serverID2, serverKey2 := createPeer(t) - serverTransport, err := NewTransport(serverKey, nil, nil) + serverTransport, err := NewTransport(serverKey, nil, nil, nil) require.NoError(t, err) defer serverTransport.(io.Closer).Close() ln1 := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic") defer ln1.Close() - serverTransport2, err := NewTransport(serverKey2, nil, nil) + serverTransport2, err := NewTransport(serverKey2, nil, nil, nil) require.NoError(t, err) defer serverTransport2.(io.Closer).Close() ln2 := runServer(t, serverTransport2, "/ip4/127.0.0.1/udp/0/quic") @@ -278,7 +389,7 @@ func TestDialTwo(t *testing.T) { } }() - clientTransport, err := NewTransport(clientKey, nil, nil) + clientTransport, err := NewTransport(clientKey, nil, nil, nil) require.NoError(t, err) defer clientTransport.(io.Closer).Close() c1, err := clientTransport.Dial(context.Background(), ln1.Multiaddr(), serverID) @@ -329,7 +440,7 @@ func TestStatelessReset(t *testing.T) { serverID, serverKey := createPeer(t) _, clientKey := createPeer(t) - serverTransport, err := NewTransport(serverKey, nil, nil) + serverTransport, err := NewTransport(serverKey, nil, nil, nil) require.NoError(t, err) defer serverTransport.(io.Closer).Close() ln := runServer(t, serverTransport, "/ip4/127.0.0.1/udp/0/quic") @@ -346,7 +457,7 @@ func TestStatelessReset(t *testing.T) { defer proxy.Close() // establish a connection - clientTransport, err := NewTransport(clientKey, nil, nil) + clientTransport, err := NewTransport(clientKey, nil, nil, nil) require.NoError(t, err) defer clientTransport.(io.Closer).Close() proxyAddr, err := toQuicMultiaddr(proxy.LocalAddr()) @@ -395,7 +506,7 @@ func TestHolePunching(t *testing.T) { serverID, serverKey := createPeer(t) clientID, clientKey := createPeer(t) - t1, err := NewTransport(serverKey, nil, nil) + t1, err := NewTransport(serverKey, nil, nil, nil) require.NoError(t, err) defer t1.(io.Closer).Close() laddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") @@ -409,7 +520,7 @@ func TestHolePunching(t *testing.T) { require.Error(t, err, "didn't expect to accept any connections") }() - t2, err := NewTransport(clientKey, nil, nil) + t2, err := NewTransport(clientKey, nil, nil, nil) require.NoError(t, err) defer t2.(io.Closer).Close() ln2, err := t2.Listen(laddr) @@ -423,7 +534,7 @@ func TestHolePunching(t *testing.T) { connChan := make(chan tpt.CapableConn) go func() { conn, err := t2.Dial( - n.WithSimultaneousConnect(context.Background(), false, ""), + network.WithSimultaneousConnect(context.Background(), false, ""), ln1.Multiaddr(), serverID, ) @@ -431,7 +542,7 @@ func TestHolePunching(t *testing.T) { connChan <- conn }() conn1, err := t1.Dial( - n.WithSimultaneousConnect(context.Background(), true, ""), + network.WithSimultaneousConnect(context.Background(), true, ""), ln2.Multiaddr(), clientID, ) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index dfa63a0484..a36fa01c7f 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -6,7 +6,7 @@ import ( "net" ic "github.com/libp2p/go-libp2p-core/crypto" - n "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" tpt "github.com/libp2p/go-libp2p-core/transport" @@ -23,6 +23,7 @@ type listener struct { quicListener quic.Listener conn *reuseConn transport *transport + rcmgr network.ResourceManager privKey ic.PrivKey localPeer peer.ID localMultiaddr ma.Multiaddr @@ -30,7 +31,7 @@ type listener struct { var _ tpt.Listener = &listener{} -func newListener(rconn *reuseConn, t *transport, localPeer peer.ID, key ic.PrivKey, identity *p2ptls.Identity) (tpt.Listener, error) { +func newListener(rconn *reuseConn, t *transport, localPeer peer.ID, key ic.PrivKey, identity *p2ptls.Identity, rcmgr network.ResourceManager) (tpt.Listener, error) { var tlsConf tls.Config tlsConf.GetConfigForClient = func(_ *tls.ClientHelloInfo) (*tls.Config, error) { // return a tls.Config that verifies the peer's certificate chain. @@ -52,6 +53,7 @@ func newListener(rconn *reuseConn, t *transport, localPeer peer.ID, key ic.PrivK conn: rconn, quicListener: ln, transport: t, + rcmgr: rcmgr, privKey: key, localPeer: localPeer, localMultiaddr: localMultiaddr, @@ -70,7 +72,8 @@ func (l *listener) Accept() (tpt.CapableConn, error) { sess.CloseWithError(0, err.Error()) continue } - if l.transport.gater != nil && !(l.transport.gater.InterceptAccept(conn) && l.transport.gater.InterceptSecured(n.DirInbound, conn.remotePeerID, conn)) { + if l.transport.gater != nil && !(l.transport.gater.InterceptAccept(conn) && l.transport.gater.InterceptSecured(network.DirInbound, conn.remotePeerID, conn)) { + conn.scope.Done() sess.CloseWithError(errorCodeConnectionGating, "connection gated") continue } @@ -94,27 +97,42 @@ func (l *listener) Accept() (tpt.CapableConn, error) { } func (l *listener) setupConn(sess quic.Session) (*conn, error) { + connScope, err := l.rcmgr.OpenConnection(network.DirInbound, false) + if err != nil { + log.Debugw("resource manager blocked incoming connection", "addr", sess.RemoteAddr(), "error", err) + return nil, err + } // The tls.Config used to establish this connection already verified the certificate chain. // Since we don't have any way of knowing which tls.Config was used though, // we have to re-determine the peer's identity here. // Therefore, this is expected to never fail. remotePubKey, err := p2ptls.PubKeyFromCertChain(sess.ConnectionState().TLS.PeerCertificates) if err != nil { + connScope.Done() return nil, err } remotePeerID, err := peer.IDFromPublicKey(remotePubKey) if err != nil { + connScope.Done() + return nil, err + } + if err := connScope.SetPeer(remotePeerID); err != nil { + log.Debugw("resource manager blocked incoming connection for peer", "peer", remotePeerID, "addr", sess.RemoteAddr(), "error", err) + connScope.Done() return nil, err } remoteMultiaddr, err := toQuicMultiaddr(sess.RemoteAddr()) if err != nil { + connScope.Done() return nil, err } + l.conn.IncreaseCount() return &conn{ sess: sess, pconn: l.conn, transport: l.transport, + scope: connScope, localPeer: l.localPeer, localMultiaddr: l.localMultiaddr, privKey: l.privKey, diff --git a/p2p/transport/quic/listener_test.go b/p2p/transport/quic/listener_test.go index 4de9da3ed1..7a72f76c57 100644 --- a/p2p/transport/quic/listener_test.go +++ b/p2p/transport/quic/listener_test.go @@ -13,6 +13,7 @@ import ( "time" ic "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/network" tpt "github.com/libp2p/go-libp2p-core/transport" "github.com/lucas-clemente/quic-go" @@ -20,12 +21,12 @@ import ( "github.com/stretchr/testify/require" ) -func newTransport(t *testing.T) tpt.Transport { +func newTransport(t *testing.T, rcmgr network.ResourceManager) tpt.Transport { rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err) key, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(rsaKey)) require.NoError(t, err) - tr, err := NewTransport(key, nil, nil) + tr, err := NewTransport(key, nil, nil, rcmgr) require.NoError(t, err) return tr } @@ -44,7 +45,7 @@ func TestConnUsedForListening(t *testing.T) { localAddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/udp/0/quic") require.NoError(t, err) - tr := newTransport(t) + tr := newTransport(t, nil) defer tr.(io.Closer).Close() _, err = tr.Listen(localAddr) require.EqualError(t, err, "listen error") @@ -55,7 +56,7 @@ func TestConnUsedForListening(t *testing.T) { } func TestListenAddr(t *testing.T) { - tr := newTransport(t) + tr := newTransport(t, nil) defer tr.(io.Closer).Close() t.Run("for IPv4", func(t *testing.T) { @@ -80,7 +81,7 @@ func TestListenAddr(t *testing.T) { } func TestAccepting(t *testing.T) { - tr := newTransport(t) + tr := newTransport(t, nil) defer tr.(io.Closer).Close() ln, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic")) require.NoError(t, err) @@ -104,7 +105,7 @@ func TestAccepting(t *testing.T) { } func TestAcceptAfterClose(t *testing.T) { - tr := newTransport(t) + tr := newTransport(t, nil) defer tr.(io.Closer).Close() ln, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic")) require.NoError(t, err) diff --git a/p2p/transport/quic/stream.go b/p2p/transport/quic/stream.go index 0c92020683..883b0cc348 100644 --- a/p2p/transport/quic/stream.go +++ b/p2p/transport/quic/stream.go @@ -3,7 +3,7 @@ package libp2pquic import ( "errors" - "github.com/libp2p/go-libp2p-core/mux" + "github.com/libp2p/go-libp2p-core/network" "github.com/lucas-clemente/quic-go" ) @@ -16,10 +16,12 @@ type stream struct { quic.Stream } +var _ network.MuxedStream = &stream{} + func (s *stream) Read(b []byte) (n int, err error) { n, err = s.Stream.Read(b) if err != nil && errors.Is(err, &quic.StreamError{}) { - err = mux.ErrReset + err = network.ErrReset } return n, err } @@ -27,7 +29,7 @@ func (s *stream) Read(b []byte) (n int, err error) { func (s *stream) Write(b []byte) (n int, err error) { n, err = s.Stream.Write(b) if err != nil && errors.Is(err, &quic.StreamError{}) { - err = mux.ErrReset + err = network.ErrReset } return n, err } @@ -51,5 +53,3 @@ func (s *stream) CloseRead() error { func (s *stream) CloseWrite() error { return s.Stream.Close() } - -var _ mux.MuxedStream = &stream{} diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index ee47213c9f..77bc7644e5 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -10,21 +10,24 @@ import ( "sync" "time" - "github.com/minio/sha256-simd" "golang.org/x/crypto/hkdf" - logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p-core/connmgr" ic "github.com/libp2p/go-libp2p-core/crypto" - n "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/pnet" tpt "github.com/libp2p/go-libp2p-core/transport" + p2ptls "github.com/libp2p/go-libp2p-tls" - "github.com/lucas-clemente/quic-go" + ma "github.com/multiformats/go-multiaddr" mafmt "github.com/multiformats/go-multiaddr-fmt" manet "github.com/multiformats/go-multiaddr/net" + + logging "github.com/ipfs/go-log/v2" + "github.com/lucas-clemente/quic-go" + "github.com/minio/sha256-simd" ) var log = logging.Logger("quic-transport") @@ -109,6 +112,7 @@ type transport struct { serverConfig *quic.Config clientConfig *quic.Config gater connmgr.ConnectionGater + rcmgr network.ResourceManager holePunchingMx sync.Mutex holePunching map[holePunchKey]*activeHolePunch @@ -127,7 +131,7 @@ type activeHolePunch struct { } // NewTransport creates a new QUIC transport -func NewTransport(key ic.PrivKey, psk pnet.PSK, gater connmgr.ConnectionGater) (tpt.Transport, error) { +func NewTransport(key ic.PrivKey, psk pnet.PSK, gater connmgr.ConnectionGater, rcmgr network.ResourceManager) (tpt.Transport, error) { if len(psk) > 0 { log.Error("QUIC doesn't support private networks yet.") return nil, errors.New("QUIC doesn't support private networks yet") @@ -144,6 +148,9 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, gater connmgr.ConnectionGater) ( if err != nil { return nil, err } + if rcmgr == nil { + rcmgr = network.NullResourceManager + } config := quicConfig.Clone() keyBytes, err := key.Raw() if err != nil { @@ -164,17 +171,18 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, gater connmgr.ConnectionGater) ( serverConfig: config, clientConfig: config.Clone(), gater: gater, + rcmgr: rcmgr, holePunching: make(map[holePunchKey]*activeHolePunch), }, nil } // Dial dials a new QUIC connection func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tpt.CapableConn, error) { - network, host, err := manet.DialArgs(raddr) + netw, host, err := manet.DialArgs(raddr) if err != nil { return nil, err } - addr, err := net.ResolveUDPAddr(network, host) + addr, err := net.ResolveUDPAddr(netw, host) if err != nil { return nil, err } @@ -183,17 +191,27 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp return nil, err } tlsConf, keyCh := t.identity.ConfigForPeer(p) - - if ok, isClient, _ := n.GetSimultaneousConnect(ctx); ok && !isClient { - return t.holePunch(ctx, network, addr, p) + if ok, isClient, _ := network.GetSimultaneousConnect(ctx); ok && !isClient { + return t.holePunch(ctx, netw, addr, p) } - pconn, err := t.connManager.Dial(network, addr) + scope, err := t.rcmgr.OpenConnection(network.DirOutbound, false) + if err != nil { + log.Debugw("resource manager blocked outgoing connection", "peer", p, "addr", raddr, "error", err) + return nil, err + } + if err := scope.SetPeer(p); err != nil { + log.Debugw("resource manager blocked outgoing connection for peer", "peer", p, "addr", raddr, "error", err) + scope.Done() + return nil, err + } + pconn, err := t.connManager.Dial(netw, addr) if err != nil { return nil, err } sess, err := quicDialContext(ctx, pconn, addr, host, tlsConf, t.clientConfig) if err != nil { + scope.Done() pconn.DecreaseCount() return nil, err } @@ -205,6 +223,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp } if remotePubKey == nil { pconn.DecreaseCount() + scope.Done() return nil, errors.New("go-libp2p-quic-transport BUG: expected remote pub key to be set") } @@ -217,6 +236,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp sess: sess, pconn: pconn, transport: t, + scope: scope, privKey: t.privKey, localPeer: t.localPeer, localMultiaddr: localMultiaddr, @@ -224,7 +244,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp remotePeerID: p, remoteMultiaddr: remoteMultiaddr, } - if t.gater != nil && !t.gater.InterceptSecured(n.DirOutbound, p, conn) { + if t.gater != nil && !t.gater.InterceptSecured(network.DirOutbound, p, conn) { sess.CloseWithError(errorCodeConnectionGating, "connection gated") return nil, fmt.Errorf("secured connection gated") } @@ -329,7 +349,7 @@ func (t *transport) Listen(addr ma.Multiaddr) (tpt.Listener, error) { if err != nil { return nil, err } - ln, err := newListener(conn, t, t.localPeer, t.privKey, t.identity) + ln, err := newListener(conn, t, t.localPeer, t.privKey, t.identity, t.rcmgr) if err != nil { conn.DecreaseCount() return nil, err diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go index 72eb0c49e9..48486a105a 100644 --- a/p2p/transport/quic/transport_test.go +++ b/p2p/transport/quic/transport_test.go @@ -26,7 +26,7 @@ func getTransport(t *testing.T) tpt.Transport { require.NoError(t, err) key, err := ic.UnmarshalRsaPrivateKey(x509.MarshalPKCS1PrivateKey(rsaKey)) require.NoError(t, err) - tr, err := NewTransport(key, nil, nil) + tr, err := NewTransport(key, nil, nil, nil) require.NoError(t, err) return tr } From ca49491d3da01d785281b07fb0a016a5bbb339f4 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 14 Jan 2022 13:00:49 +0400 Subject: [PATCH 135/138] use the resource manager to allow connection flow control window increases --- p2p/transport/quic/conn.go | 7 ++++- p2p/transport/quic/listener.go | 13 +++++----- p2p/transport/quic/transport.go | 45 ++++++++++++++++++++++++++++----- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index d35ebf4d7b..990180cf34 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -15,7 +15,7 @@ import ( type conn struct { sess quic.Session pconn *reuseConn - transport tpt.Transport + transport *transport scope network.ConnManagementScope localPeer peer.ID @@ -33,6 +33,7 @@ var _ tpt.CapableConn = &conn{} // It must be called even if the peer closed the connection in order for // garbage collection to properly work in this package. func (c *conn) Close() error { + c.transport.removeConn(c.sess) err := c.sess.CloseWithError(0, "") c.pconn.DecreaseCount() c.scope.Done() @@ -44,6 +45,10 @@ func (c *conn) IsClosed() bool { return c.sess.Context().Err() != nil } +func (c *conn) allowWindowIncrease(size uint64) bool { + return c.scope.ReserveMemory(int(size), network.ReservationPriorityMedium) == nil +} + // OpenStream creates a new stream. func (c *conn) OpenStream(ctx context.Context) (network.MuxedStream, error) { qstr, err := c.sess.OpenStreamSync(ctx) diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index a36fa01c7f..093e9ce48e 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -67,24 +67,25 @@ func (l *listener) Accept() (tpt.CapableConn, error) { if err != nil { return nil, err } - conn, err := l.setupConn(sess) + c, err := l.setupConn(sess) if err != nil { sess.CloseWithError(0, err.Error()) continue } - if l.transport.gater != nil && !(l.transport.gater.InterceptAccept(conn) && l.transport.gater.InterceptSecured(network.DirInbound, conn.remotePeerID, conn)) { - conn.scope.Done() + if l.transport.gater != nil && !(l.transport.gater.InterceptAccept(c) && l.transport.gater.InterceptSecured(network.DirInbound, c.remotePeerID, c)) { + c.scope.Done() sess.CloseWithError(errorCodeConnectionGating, "connection gated") continue } + l.transport.addConn(sess, c) // return through active hole punching if any - key := holePunchKey{addr: sess.RemoteAddr().String(), peer: conn.remotePeerID} + key := holePunchKey{addr: sess.RemoteAddr().String(), peer: c.remotePeerID} var wasHolePunch bool l.transport.holePunchingMx.Lock() holePunch, ok := l.transport.holePunching[key] if ok && !holePunch.fulfilled { - holePunch.connCh <- conn + holePunch.connCh <- c wasHolePunch = true holePunch.fulfilled = true } @@ -92,7 +93,7 @@ func (l *listener) Accept() (tpt.CapableConn, error) { if wasHolePunch { continue } - return conn, nil + return c, nil } } diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 77bc7644e5..bc1b39d111 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -116,6 +116,9 @@ type transport struct { holePunchingMx sync.Mutex holePunching map[holePunchKey]*activeHolePunch + + connMx sync.Mutex + conns map[quic.Session]*conn } var _ tpt.Transport = &transport{} @@ -163,17 +166,20 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, gater connmgr.ConnectionGater, r } config.Tracer = tracer - return &transport{ + tr := &transport{ privKey: key, localPeer: localPeer, identity: identity, connManager: connManager, - serverConfig: config, - clientConfig: config.Clone(), gater: gater, rcmgr: rcmgr, + conns: make(map[quic.Session]*conn), holePunching: make(map[holePunchKey]*activeHolePunch), - }, nil + } + config.AllowConnectionWindowIncrease = tr.allowWindowIncrease + tr.serverConfig = config + tr.clientConfig = config.Clone() + return tr, nil } // Dial dials a new QUIC connection @@ -232,7 +238,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp sess.CloseWithError(0, "") return nil, err } - conn := &conn{ + c := &conn{ sess: sess, pconn: pconn, transport: t, @@ -244,11 +250,24 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp remotePeerID: p, remoteMultiaddr: remoteMultiaddr, } - if t.gater != nil && !t.gater.InterceptSecured(network.DirOutbound, p, conn) { + if t.gater != nil && !t.gater.InterceptSecured(network.DirOutbound, p, c) { sess.CloseWithError(errorCodeConnectionGating, "connection gated") return nil, fmt.Errorf("secured connection gated") } - return conn, nil + t.addConn(sess, c) + return c, nil +} + +func (t *transport) addConn(sess quic.Session, c *conn) { + t.connMx.Lock() + t.conns[sess] = c + t.connMx.Unlock() +} + +func (t *transport) removeConn(sess quic.Session) { + t.connMx.Lock() + delete(t.conns, sess) + t.connMx.Unlock() } func (t *transport) holePunch(ctx context.Context, network string, addr *net.UDPAddr, p peer.ID) (tpt.CapableConn, error) { @@ -357,6 +376,18 @@ func (t *transport) Listen(addr ma.Multiaddr) (tpt.Listener, error) { return ln, nil } +func (t *transport) allowWindowIncrease(sess quic.Session, size uint64) bool { + // If the QUIC session tries to increase the window before we've inserted it + // into our connections map (which we do right after dialing / accepting it), + // we have no way to account for that memory. This should be very rare. + // Block this attempt. The session can request more memory later. + c, ok := t.conns[sess] + if !ok { + return false + } + return c.allowWindowIncrease(size) +} + // Proxy returns true if this transport proxies. func (t *transport) Proxy() bool { return false From add734f37438b1cbc64f942a6647f31dcaa5f944 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Mon, 7 Feb 2022 03:21:12 +0100 Subject: [PATCH 136/138] Prevent data race in allowWindowIncrease (#259) --- p2p/transport/quic/transport.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index bc1b39d111..1c46cf30f8 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -381,7 +381,9 @@ func (t *transport) allowWindowIncrease(sess quic.Session, size uint64) bool { // into our connections map (which we do right after dialing / accepting it), // we have no way to account for that memory. This should be very rare. // Block this attempt. The session can request more memory later. + t.connMx.Lock() c, ok := t.conns[sess] + t.connMx.Unlock() if !ok { return false } From 8d522620aaf8664cfa1614e40e0e893e31c43b99 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Wed, 6 Apr 2022 13:48:57 +0100 Subject: [PATCH 137/138] update quic-go to v0.27.0 (#264) --- p2p/transport/quic/conn.go | 12 +++++------ p2p/transport/quic/listener.go | 24 +++++++++++----------- p2p/transport/quic/transport.go | 30 ++++++++++++++-------------- p2p/transport/quic/transport_test.go | 2 +- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/p2p/transport/quic/conn.go b/p2p/transport/quic/conn.go index 990180cf34..778460c6c9 100644 --- a/p2p/transport/quic/conn.go +++ b/p2p/transport/quic/conn.go @@ -13,7 +13,7 @@ import ( ) type conn struct { - sess quic.Session + quicConn quic.Connection pconn *reuseConn transport *transport scope network.ConnManagementScope @@ -33,8 +33,8 @@ var _ tpt.CapableConn = &conn{} // It must be called even if the peer closed the connection in order for // garbage collection to properly work in this package. func (c *conn) Close() error { - c.transport.removeConn(c.sess) - err := c.sess.CloseWithError(0, "") + c.transport.removeConn(c.quicConn) + err := c.quicConn.CloseWithError(0, "") c.pconn.DecreaseCount() c.scope.Done() return err @@ -42,7 +42,7 @@ func (c *conn) Close() error { // IsClosed returns whether a connection is fully closed. func (c *conn) IsClosed() bool { - return c.sess.Context().Err() != nil + return c.quicConn.Context().Err() != nil } func (c *conn) allowWindowIncrease(size uint64) bool { @@ -51,13 +51,13 @@ func (c *conn) allowWindowIncrease(size uint64) bool { // OpenStream creates a new stream. func (c *conn) OpenStream(ctx context.Context) (network.MuxedStream, error) { - qstr, err := c.sess.OpenStreamSync(ctx) + qstr, err := c.quicConn.OpenStreamSync(ctx) return &stream{Stream: qstr}, err } // AcceptStream accepts a stream opened by the other side. func (c *conn) AcceptStream() (network.MuxedStream, error) { - qstr, err := c.sess.AcceptStream(context.Background()) + qstr, err := c.quicConn.AcceptStream(context.Background()) return &stream{Stream: qstr}, err } diff --git a/p2p/transport/quic/listener.go b/p2p/transport/quic/listener.go index 093e9ce48e..d6a20d22a4 100644 --- a/p2p/transport/quic/listener.go +++ b/p2p/transport/quic/listener.go @@ -63,24 +63,24 @@ func newListener(rconn *reuseConn, t *transport, localPeer peer.ID, key ic.PrivK // Accept accepts new connections. func (l *listener) Accept() (tpt.CapableConn, error) { for { - sess, err := l.quicListener.Accept(context.Background()) + qconn, err := l.quicListener.Accept(context.Background()) if err != nil { return nil, err } - c, err := l.setupConn(sess) + c, err := l.setupConn(qconn) if err != nil { - sess.CloseWithError(0, err.Error()) + qconn.CloseWithError(0, err.Error()) continue } if l.transport.gater != nil && !(l.transport.gater.InterceptAccept(c) && l.transport.gater.InterceptSecured(network.DirInbound, c.remotePeerID, c)) { c.scope.Done() - sess.CloseWithError(errorCodeConnectionGating, "connection gated") + qconn.CloseWithError(errorCodeConnectionGating, "connection gated") continue } - l.transport.addConn(sess, c) + l.transport.addConn(qconn, c) // return through active hole punching if any - key := holePunchKey{addr: sess.RemoteAddr().String(), peer: c.remotePeerID} + key := holePunchKey{addr: qconn.RemoteAddr().String(), peer: c.remotePeerID} var wasHolePunch bool l.transport.holePunchingMx.Lock() holePunch, ok := l.transport.holePunching[key] @@ -97,17 +97,17 @@ func (l *listener) Accept() (tpt.CapableConn, error) { } } -func (l *listener) setupConn(sess quic.Session) (*conn, error) { +func (l *listener) setupConn(qconn quic.Connection) (*conn, error) { connScope, err := l.rcmgr.OpenConnection(network.DirInbound, false) if err != nil { - log.Debugw("resource manager blocked incoming connection", "addr", sess.RemoteAddr(), "error", err) + log.Debugw("resource manager blocked incoming connection", "addr", qconn.RemoteAddr(), "error", err) return nil, err } // The tls.Config used to establish this connection already verified the certificate chain. // Since we don't have any way of knowing which tls.Config was used though, // we have to re-determine the peer's identity here. // Therefore, this is expected to never fail. - remotePubKey, err := p2ptls.PubKeyFromCertChain(sess.ConnectionState().TLS.PeerCertificates) + remotePubKey, err := p2ptls.PubKeyFromCertChain(qconn.ConnectionState().TLS.PeerCertificates) if err != nil { connScope.Done() return nil, err @@ -118,11 +118,11 @@ func (l *listener) setupConn(sess quic.Session) (*conn, error) { return nil, err } if err := connScope.SetPeer(remotePeerID); err != nil { - log.Debugw("resource manager blocked incoming connection for peer", "peer", remotePeerID, "addr", sess.RemoteAddr(), "error", err) + log.Debugw("resource manager blocked incoming connection for peer", "peer", remotePeerID, "addr", qconn.RemoteAddr(), "error", err) connScope.Done() return nil, err } - remoteMultiaddr, err := toQuicMultiaddr(sess.RemoteAddr()) + remoteMultiaddr, err := toQuicMultiaddr(qconn.RemoteAddr()) if err != nil { connScope.Done() return nil, err @@ -130,7 +130,7 @@ func (l *listener) setupConn(sess quic.Session) (*conn, error) { l.conn.IncreaseCount() return &conn{ - sess: sess, + quicConn: qconn, pconn: l.conn, transport: l.transport, scope: connScope, diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index 1c46cf30f8..b06f7bfec5 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -118,7 +118,7 @@ type transport struct { holePunching map[holePunchKey]*activeHolePunch connMx sync.Mutex - conns map[quic.Session]*conn + conns map[quic.Connection]*conn } var _ tpt.Transport = &transport{} @@ -173,7 +173,7 @@ func NewTransport(key ic.PrivKey, psk pnet.PSK, gater connmgr.ConnectionGater, r connManager: connManager, gater: gater, rcmgr: rcmgr, - conns: make(map[quic.Session]*conn), + conns: make(map[quic.Connection]*conn), holePunching: make(map[holePunchKey]*activeHolePunch), } config.AllowConnectionWindowIncrease = tr.allowWindowIncrease @@ -215,7 +215,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if err != nil { return nil, err } - sess, err := quicDialContext(ctx, pconn, addr, host, tlsConf, t.clientConfig) + qconn, err := quicDialContext(ctx, pconn, addr, host, tlsConf, t.clientConfig) if err != nil { scope.Done() pconn.DecreaseCount() @@ -235,11 +235,11 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp localMultiaddr, err := toQuicMultiaddr(pconn.LocalAddr()) if err != nil { - sess.CloseWithError(0, "") + qconn.CloseWithError(0, "") return nil, err } c := &conn{ - sess: sess, + quicConn: qconn, pconn: pconn, transport: t, scope: scope, @@ -251,22 +251,22 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp remoteMultiaddr: remoteMultiaddr, } if t.gater != nil && !t.gater.InterceptSecured(network.DirOutbound, p, c) { - sess.CloseWithError(errorCodeConnectionGating, "connection gated") + qconn.CloseWithError(errorCodeConnectionGating, "connection gated") return nil, fmt.Errorf("secured connection gated") } - t.addConn(sess, c) + t.addConn(qconn, c) return c, nil } -func (t *transport) addConn(sess quic.Session, c *conn) { +func (t *transport) addConn(conn quic.Connection, c *conn) { t.connMx.Lock() - t.conns[sess] = c + t.conns[conn] = c t.connMx.Unlock() } -func (t *transport) removeConn(sess quic.Session) { +func (t *transport) removeConn(conn quic.Connection) { t.connMx.Lock() - delete(t.conns, sess) + delete(t.conns, conn) t.connMx.Unlock() } @@ -376,13 +376,13 @@ func (t *transport) Listen(addr ma.Multiaddr) (tpt.Listener, error) { return ln, nil } -func (t *transport) allowWindowIncrease(sess quic.Session, size uint64) bool { - // If the QUIC session tries to increase the window before we've inserted it +func (t *transport) allowWindowIncrease(conn quic.Connection, size uint64) bool { + // If the QUIC connection tries to increase the window before we've inserted it // into our connections map (which we do right after dialing / accepting it), // we have no way to account for that memory. This should be very rare. - // Block this attempt. The session can request more memory later. + // Block this attempt. The connection can request more memory later. t.connMx.Lock() - c, ok := t.conns[sess] + c, ok := t.conns[conn] t.connMx.Unlock() if !ok { return false diff --git a/p2p/transport/quic/transport_test.go b/p2p/transport/quic/transport_test.go index 48486a105a..64bf24e0ae 100644 --- a/p2p/transport/quic/transport_test.go +++ b/p2p/transport/quic/transport_test.go @@ -83,7 +83,7 @@ func TestConnectionPassedToQUIC(t *testing.T) { defer func() { quicDialContext = origQuicDialContext }() var conn net.PacketConn - quicDialContext = func(_ context.Context, c net.PacketConn, _ net.Addr, _ string, _ *tls.Config, _ *quic.Config) (quic.Session, error) { + quicDialContext = func(_ context.Context, c net.PacketConn, _ net.Addr, _ string, _ *tls.Config, _ *quic.Config) (quic.Connection, error) { conn = c return nil, errors.New("listen error") } From 4810afb29326aef1c9fbe9a3699bd75cf83e63de Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 22 Apr 2022 16:34:30 +0100 Subject: [PATCH 138/138] switch from github.com/libp2p/go-libp2p-quic-transport to p2p/transport/quic --- defaults.go | 2 +- go.mod | 12 ++++++------ p2p/net/swarm/dial_worker_test.go | 2 +- p2p/net/swarm/testing/testing.go | 2 +- p2p/transport/quic/cmd/client/main.go | 2 +- p2p/transport/quic/cmd/server/main.go | 2 +- p2p/transport/quic/reuse_test.go | 4 +++- p2p/transport/quic/transport.go | 2 +- 8 files changed, 15 insertions(+), 13 deletions(-) diff --git a/defaults.go b/defaults.go index a4a446cb54..4c881114aa 100644 --- a/defaults.go +++ b/defaults.go @@ -6,6 +6,7 @@ import ( "crypto/rand" "github.com/libp2p/go-libp2p/p2p/net/connmgr" + quic "github.com/libp2p/go-libp2p/p2p/transport/quic" "github.com/libp2p/go-libp2p/p2p/transport/tcp" ws "github.com/libp2p/go-libp2p/p2p/transport/websocket" @@ -13,7 +14,6 @@ import ( noise "github.com/libp2p/go-libp2p-noise" "github.com/libp2p/go-libp2p-peerstore/pstoremem" - quic "github.com/libp2p/go-libp2p-quic-transport" rcmgr "github.com/libp2p/go-libp2p-resource-manager" tls "github.com/libp2p/go-libp2p-tls" yamux "github.com/libp2p/go-libp2p-yamux" diff --git a/go.mod b/go.mod index 905f22d72e..13441b6d41 100644 --- a/go.mod +++ b/go.mod @@ -6,12 +6,14 @@ require ( github.com/benbjohnson/clock v1.3.0 github.com/gogo/protobuf v1.3.2 github.com/golang/mock v1.6.0 + github.com/google/gopacket v1.1.19 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 github.com/ipfs/go-ipfs-util v0.0.2 github.com/ipfs/go-log/v2 v2.5.1 + github.com/klauspost/compress v1.15.1 github.com/libp2p/go-buffer-pool v0.0.2 github.com/libp2p/go-conn-security-multistream v0.3.0 github.com/libp2p/go-eventbus v0.2.1 @@ -22,7 +24,6 @@ require ( github.com/libp2p/go-libp2p-nat v0.1.0 github.com/libp2p/go-libp2p-noise v0.4.0 github.com/libp2p/go-libp2p-peerstore v0.6.0 - github.com/libp2p/go-libp2p-quic-transport v0.17.0 github.com/libp2p/go-libp2p-resource-manager v0.2.1 github.com/libp2p/go-libp2p-testing v0.9.2 github.com/libp2p/go-libp2p-tls v0.4.1 @@ -34,8 +35,10 @@ require ( github.com/libp2p/go-reuseport-transport v0.1.0 github.com/libp2p/go-stream-muxer-multistream v0.4.0 github.com/libp2p/zeroconf/v2 v2.1.1 + github.com/lucas-clemente/quic-go v0.27.0 github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b + github.com/minio/sha256-simd v1.0.0 github.com/multiformats/go-multiaddr v0.5.0 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multiaddr-fmt v0.1.0 @@ -47,6 +50,7 @@ require ( github.com/stretchr/testify v1.7.0 github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9 github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 + golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c ) @@ -68,26 +72,24 @@ require ( github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect 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/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 github.com/jbenet/goprocess v0.1.4 // indirect - github.com/klauspost/compress v1.15.1 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect github.com/koron/go-ssdp v0.0.2 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.0.3 // indirect github.com/libp2p/go-libp2p-blankhost v0.3.0 // indirect github.com/libp2p/go-libp2p-pnet v0.2.0 // indirect + github.com/libp2p/go-libp2p-quic-transport v0.17.0 // indirect github.com/libp2p/go-libp2p-swarm v0.10.2 // indirect github.com/libp2p/go-mplex v0.4.0 // indirect github.com/libp2p/go-nat v0.1.0 // indirect github.com/libp2p/go-openssl v0.0.7 // indirect github.com/libp2p/go-tcp-transport v0.5.1 // indirect github.com/libp2p/go-yamux/v3 v3.1.1 // indirect - github.com/lucas-clemente/quic-go v0.27.0 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect @@ -96,7 +98,6 @@ require ( github.com/miekg/dns v1.1.48 // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect - github.com/minio/sha256-simd v1.0.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.0.4 // indirect github.com/multiformats/go-base36 v0.1.0 // indirect @@ -117,7 +118,6 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.21.0 // indirect - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 // indirect golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect diff --git a/p2p/net/swarm/dial_worker_test.go b/p2p/net/swarm/dial_worker_test.go index 3d74d62687..2fec019a8e 100644 --- a/p2p/net/swarm/dial_worker_test.go +++ b/p2p/net/swarm/dial_worker_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + quic "github.com/libp2p/go-libp2p/p2p/transport/quic" "github.com/libp2p/go-libp2p/p2p/transport/tcp" "github.com/libp2p/go-libp2p-core/peerstore" @@ -16,7 +17,6 @@ import ( csms "github.com/libp2p/go-conn-security-multistream" "github.com/libp2p/go-libp2p-peerstore/pstoremem" - quic "github.com/libp2p/go-libp2p-quic-transport" tnet "github.com/libp2p/go-libp2p-testing/net" tptu "github.com/libp2p/go-libp2p-transport-upgrader" yamux "github.com/libp2p/go-libp2p-yamux" diff --git a/p2p/net/swarm/testing/testing.go b/p2p/net/swarm/testing/testing.go index b6289182c3..c529e814b7 100644 --- a/p2p/net/swarm/testing/testing.go +++ b/p2p/net/swarm/testing/testing.go @@ -5,6 +5,7 @@ import ( "time" "github.com/libp2p/go-libp2p/p2p/net/swarm" + quic "github.com/libp2p/go-libp2p/p2p/transport/quic" "github.com/libp2p/go-libp2p/p2p/transport/tcp" "github.com/libp2p/go-libp2p-core/connmgr" @@ -19,7 +20,6 @@ import ( csms "github.com/libp2p/go-conn-security-multistream" "github.com/libp2p/go-libp2p-peerstore/pstoremem" - quic "github.com/libp2p/go-libp2p-quic-transport" tnet "github.com/libp2p/go-libp2p-testing/net" tptu "github.com/libp2p/go-libp2p-transport-upgrader" yamux "github.com/libp2p/go-libp2p-yamux" diff --git a/p2p/transport/quic/cmd/client/main.go b/p2p/transport/quic/cmd/client/main.go index 50889cf1b6..c67a827686 100644 --- a/p2p/transport/quic/cmd/client/main.go +++ b/p2p/transport/quic/cmd/client/main.go @@ -10,7 +10,7 @@ import ( ic "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/peer" - libp2pquic "github.com/libp2p/go-libp2p-quic-transport" + libp2pquic "github.com/libp2p/go-libp2p/p2p/transport/quic" ma "github.com/multiformats/go-multiaddr" ) diff --git a/p2p/transport/quic/cmd/server/main.go b/p2p/transport/quic/cmd/server/main.go index cb5b528461..0e2cf09323 100644 --- a/p2p/transport/quic/cmd/server/main.go +++ b/p2p/transport/quic/cmd/server/main.go @@ -10,7 +10,7 @@ import ( ic "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/peer" tpt "github.com/libp2p/go-libp2p-core/transport" - libp2pquic "github.com/libp2p/go-libp2p-quic-transport" + libp2pquic "github.com/libp2p/go-libp2p/p2p/transport/quic" ma "github.com/multiformats/go-multiaddr" ) diff --git a/p2p/transport/quic/reuse_test.go b/p2p/transport/quic/reuse_test.go index 46fb821ebc..122b077d9a 100644 --- a/p2p/transport/quic/reuse_test.go +++ b/p2p/transport/quic/reuse_test.go @@ -43,7 +43,7 @@ func platformHasRoutingTables() bool { func isGarbageCollectorRunning() bool { var b bytes.Buffer pprof.Lookup("goroutine").WriteTo(&b, 1) - return strings.Contains(b.String(), "go-libp2p-quic-transport.(*reuse).gc") + return strings.Contains(b.String(), "quic.(*reuse).gc") } func cleanup(t *testing.T, reuse *reuse) { @@ -56,6 +56,7 @@ func cleanup(t *testing.T, reuse *reuse) { func TestReuseListenOnAllIPv4(t *testing.T) { reuse := newReuse() + require.Eventually(t, isGarbageCollectorRunning, 100*time.Millisecond, time.Millisecond, "expected garbage collector to be running") cleanup(t, reuse) addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:0") @@ -67,6 +68,7 @@ func TestReuseListenOnAllIPv4(t *testing.T) { func TestReuseListenOnAllIPv6(t *testing.T) { reuse := newReuse() + require.Eventually(t, isGarbageCollectorRunning, 100*time.Millisecond, time.Millisecond, "expected garbage collector to be running") cleanup(t, reuse) addr, err := net.ResolveUDPAddr("udp6", "[::]:1234") diff --git a/p2p/transport/quic/transport.go b/p2p/transport/quic/transport.go index b06f7bfec5..fbc8abc75b 100644 --- a/p2p/transport/quic/transport.go +++ b/p2p/transport/quic/transport.go @@ -230,7 +230,7 @@ func (t *transport) Dial(ctx context.Context, raddr ma.Multiaddr, p peer.ID) (tp if remotePubKey == nil { pconn.DecreaseCount() scope.Done() - return nil, errors.New("go-libp2p-quic-transport BUG: expected remote pub key to be set") + return nil, errors.New("p2p/transport/quic BUG: expected remote pub key to be set") } localMultiaddr, err := toQuicMultiaddr(pconn.LocalAddr())