diff --git a/cmd/bee/cmd/cmd.go b/cmd/bee/cmd/cmd.go index 77a5d4bd6f7..2ae489880a8 100644 --- a/cmd/bee/cmd/cmd.go +++ b/cmd/bee/cmd/cmd.go @@ -33,6 +33,7 @@ const ( optionNameP2PAddr = "p2p-addr" optionNameNATAddr = "nat-addr" optionNameP2PWSEnable = "p2p-ws-enable" + optionNameP2PQUICEnable = "p2p-quic-enable" optionNameDebugAPIEnable = "debug-api-enable" optionNameDebugAPIAddr = "debug-api-addr" optionNameBootnodes = "bootnode" @@ -249,6 +250,7 @@ func (c *command) setAllFlags(cmd *cobra.Command) { cmd.Flags().String(optionNameAPIAddr, ":1633", "HTTP API listen address") cmd.Flags().String(optionNameP2PAddr, ":1634", "P2P listen address") cmd.Flags().String(optionNameNATAddr, "", "NAT exposed address") + cmd.Flags().Bool(optionNameP2PQUICEnable, true, "enable P2P QUIC transport") cmd.Flags().Bool(optionNameP2PWSEnable, false, "enable P2P WebSocket transport") cmd.Flags().StringSlice(optionNameBootnodes, []string{""}, "initial nodes to connect to") cmd.Flags().Bool(optionNameDebugAPIEnable, false, "enable debug HTTP API") diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index 504e1ab562d..920054e7188 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -303,6 +303,7 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo DebugAPIAddr: debugAPIAddr, Addr: c.config.GetString(optionNameP2PAddr), NATAddr: c.config.GetString(optionNameNATAddr), + EnableQUIC: c.config.GetBool(optionNameP2PQUICEnable), EnableWS: c.config.GetBool(optionNameP2PWSEnable), WelcomeMessage: c.config.GetString(optionWelcomeMessage), Bootnodes: networkConfig.bootNodes, diff --git a/openapi/SwarmCommon.yaml b/openapi/SwarmCommon.yaml index 854e1821287..7c5e7992704 100644 --- a/openapi/SwarmCommon.yaml +++ b/openapi/SwarmCommon.yaml @@ -320,7 +320,18 @@ components: type: array nullable: false items: - $ref: "#/components/schemas/Address" + type: object + properties: + address: + $ref: "#/components/schemas/Address" + fullNode: + type: boolean + p2pTransport: + type: array + nullable: true + items: + type: string + BlockListedPeers: type: array diff --git a/pkg/api/p2p_test.go b/pkg/api/p2p_test.go index e184906734a..336a4f7a1be 100644 --- a/pkg/api/p2p_test.go +++ b/pkg/api/p2p_test.go @@ -35,6 +35,7 @@ func TestAddresses(t *testing.T) { addresses := []multiaddr.Multiaddr{ mustMultiaddr(t, "/ip4/127.0.0.1/tcp/7071/p2p/16Uiu2HAmTBuJT9LvNmBiQiNoTsxE5mtNy6YG3paw79m94CRa9sRb"), mustMultiaddr(t, "/ip4/192.168.0.101/tcp/7071/p2p/16Uiu2HAmTBuJT9LvNmBiQiNoTsxE5mtNy6YG3paw79m94CRa9sRb"), + mustMultiaddr(t, "/ip4/127.0.0.1/udp/7071/quic-v1/p2p/16Uiu2HAmTBuJT9LvNmBiQiNoTsxE5mtNy6YG3paw79m94CRa9sRb"), } ethereumAddress := common.HexToAddress("abcd") diff --git a/pkg/api/peer.go b/pkg/api/peer.go index a6dfc8f7aeb..b35f3aac75b 100644 --- a/pkg/api/peer.go +++ b/pkg/api/peer.go @@ -13,6 +13,7 @@ import ( "github.com/ethersphere/bee/pkg/swarm" "github.com/gorilla/mux" "github.com/multiformats/go-multiaddr" + "golang.org/x/exp/maps" ) type peerConnectResponse struct { @@ -79,8 +80,9 @@ func (s *Service) peerDisconnectHandler(w http.ResponseWriter, r *http.Request) // Peer holds information about a Peer. type Peer struct { - Address swarm.Address `json:"address"` - FullNode bool `json:"fullNode"` + Address swarm.Address `json:"address"` + FullNode bool `json:"fullNode"` + P2PTransport []string `json:"p2pTransport,omitempty"` } type BlockListedPeer struct { @@ -122,8 +124,9 @@ func mapPeers(peers []p2p.Peer) (out []Peer) { out = make([]Peer, 0, len(peers)) for _, peer := range peers { out = append(out, Peer{ - Address: peer.Address, - FullNode: peer.FullNode, + Address: peer.Address, + FullNode: peer.FullNode, + P2PTransport: maps.Keys(peer.P2PTransport), }) } return diff --git a/pkg/node/node.go b/pkg/node/node.go index 3abfeb264e8..edc353fe752 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -131,6 +131,7 @@ type Options struct { DebugAPIAddr string Addr string NATAddr string + EnableQUIC bool EnableWS bool WelcomeMessage string Bootnodes []string @@ -639,6 +640,7 @@ func NewBee( p2ps, err := libp2p.New(ctx, signer, networkID, swarmAddress, addr, addressbook, stateStore, lightNodes, logger, tracer, libp2p.Options{ PrivateKey: libp2pPrivateKey, NATAddr: o.NATAddr, + EnableQUIC: o.EnableQUIC, EnableWS: o.EnableWS, WelcomeMessage: o.WelcomeMessage, FullNode: o.FullNodeMode, diff --git a/pkg/p2p/libp2p/connections_test.go b/pkg/p2p/libp2p/connections_test.go index 6f560e3ed7d..9179d87039b 100644 --- a/pkg/p2p/libp2p/connections_test.go +++ b/pkg/p2p/libp2p/connections_test.go @@ -1129,6 +1129,37 @@ func TestReachabilityUpdate(t *testing.T) { } } +func TestConnectWithEnabledQUICTransports(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + n1, overlay1 := newService(t, 1, libp2pServiceOpts{ + libp2pOpts: libp2p.Options{ + EnableQUIC: true, + FullNode: true, + }, + }) + + n2, overlay2 := newService(t, 1, libp2pServiceOpts{ + libp2pOpts: libp2p.Options{ + EnableQUIC: true, + FullNode: true, + }, + }) + + addr1 := serviceUnderlayAddress(t, n1, ma.P_QUIC_V1) + if addr1 == nil { + t.Fatal("QUIC protocol address not found") + } + + if _, err := n2.Connect(ctx, addr1); err != nil { + t.Fatal(err) + } + + expectPeers(t, n2, overlay1) + expectPeersEventually(t, n1, overlay2) +} + func testUserAgentLogLine(t *testing.T, logs *buffer, substring string) { t.Helper() diff --git a/pkg/p2p/libp2p/libp2p.go b/pkg/p2p/libp2p/libp2p.go index d7250a517fb..9f3ce012558 100644 --- a/pkg/p2p/libp2p/libp2p.go +++ b/pkg/p2p/libp2p/libp2p.go @@ -17,46 +17,45 @@ import ( "sync" "time" + ocprom "contrib.go.opencensus.io/exporter/prometheus" "github.com/ethersphere/bee" "github.com/ethersphere/bee/pkg/addressbook" "github.com/ethersphere/bee/pkg/bzz" beecrypto "github.com/ethersphere/bee/pkg/crypto" "github.com/ethersphere/bee/pkg/log" + m2 "github.com/ethersphere/bee/pkg/metrics" "github.com/ethersphere/bee/pkg/p2p" "github.com/ethersphere/bee/pkg/p2p/libp2p/internal/blocklist" "github.com/ethersphere/bee/pkg/p2p/libp2p/internal/breaker" - handshake "github.com/ethersphere/bee/pkg/p2p/libp2p/internal/handshake" + "github.com/ethersphere/bee/pkg/p2p/libp2p/internal/handshake" "github.com/ethersphere/bee/pkg/p2p/libp2p/internal/reacher" "github.com/ethersphere/bee/pkg/storage" "github.com/ethersphere/bee/pkg/swarm" "github.com/ethersphere/bee/pkg/topology" "github.com/ethersphere/bee/pkg/topology/lightnode" "github.com/ethersphere/bee/pkg/tracing" - libp2p "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/event" "github.com/libp2p/go-libp2p/core/host" - network "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/network" libp2ppeer "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peerstore" - protocol "github.com/libp2p/go-libp2p/core/protocol" - autonat "github.com/libp2p/go-libp2p/p2p/host/autonat" + "github.com/libp2p/go-libp2p/core/protocol" + "github.com/libp2p/go-libp2p/p2p/host/autonat" basichost "github.com/libp2p/go-libp2p/p2p/host/basic" "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem" rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" + rcmgrObs "github.com/libp2p/go-libp2p/p2p/host/resource-manager" lp2pswarm "github.com/libp2p/go-libp2p/p2p/net/swarm" libp2pping "github.com/libp2p/go-libp2p/p2p/protocol/ping" + libp2pquic "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" - ma "github.com/multiformats/go-multiaddr" "github.com/multiformats/go-multistream" - "go.uber.org/atomic" - - ocprom "contrib.go.opencensus.io/exporter/prometheus" - m2 "github.com/ethersphere/bee/pkg/metrics" - rcmgrObs "github.com/libp2p/go-libp2p/p2p/host/resource-manager" "github.com/prometheus/client_golang/prometheus" + "go.uber.org/atomic" ) // loggerName is the tree path name of the logger for this package. @@ -122,6 +121,7 @@ type lightnodes interface { type Options struct { PrivateKey *ecdsa.PrivateKey NATAddr string + EnableQUIC bool EnableWS bool FullNode bool LightNodeLimit int @@ -156,6 +156,9 @@ func New(ctx context.Context, signer beecrypto.Signer, networkID uint64, overlay var listenAddrs []string if ip4Addr != "" { listenAddrs = append(listenAddrs, fmt.Sprintf("/ip4/%s/tcp/%s", ip4Addr, port)) + if o.EnableQUIC { + listenAddrs = append(listenAddrs, fmt.Sprintf("/ip4/%s/udp/%s/quic-v1", ip4Addr, port)) + } if o.EnableWS { listenAddrs = append(listenAddrs, fmt.Sprintf("/ip4/%s/tcp/%s/ws", ip4Addr, port)) } @@ -163,6 +166,9 @@ func New(ctx context.Context, signer beecrypto.Signer, networkID uint64, overlay if ip6Addr != "" { listenAddrs = append(listenAddrs, fmt.Sprintf("/ip6/%s/tcp/%s", ip6Addr, port)) + if o.EnableQUIC { + listenAddrs = append(listenAddrs, fmt.Sprintf("/ip6/%s/udp/%s/quic-v1", ip6Addr, port)) + } if o.EnableWS { listenAddrs = append(listenAddrs, fmt.Sprintf("/ip6/%s/tcp/%s/ws", ip6Addr, port)) } @@ -244,7 +250,9 @@ func New(ctx context.Context, signer beecrypto.Signer, networkID uint64, overlay transports := []libp2p.Option{ libp2p.Transport(tcp.NewTCPTransport, tcp.DisableReuseport()), } - + if o.EnableQUIC { + transports = append(transports, libp2p.Transport(libp2pquic.NewTransport)) + } if o.EnableWS { transports = append(transports, libp2p.Transport(ws.New)) } @@ -695,7 +703,7 @@ func (s *Service) Blocklist(overlay swarm.Address, duration time.Duration, reaso } func buildHostAddress(peerID libp2ppeer.ID) (ma.Multiaddr, error) { - return ma.NewMultiaddr(fmt.Sprintf("/p2p/%s", peerID.Pretty())) + return ma.NewMultiaddr(fmt.Sprintf("/p2p/%s", peerID)) } func buildUnderlayAddress(addr ma.Multiaddr, peerID libp2ppeer.ID) (ma.Multiaddr, error) { diff --git a/pkg/p2p/libp2p/libp2p_test.go b/pkg/p2p/libp2p/libp2p_test.go index 378d4dbc9d3..769694f382e 100644 --- a/pkg/p2p/libp2p/libp2p_test.go +++ b/pkg/p2p/libp2p/libp2p_test.go @@ -152,12 +152,30 @@ func expectPeersEventually(t *testing.T, s *libp2p.Service, addrs ...swarm.Addre } } -func serviceUnderlayAddress(t *testing.T, s *libp2p.Service) multiaddr.Multiaddr { +// serviceUnderlayAddress returns the first multiaddress of the service that +// contains the given protocol code. If no protocol code is given, the first +// address is returned. +func serviceUnderlayAddress(t *testing.T, s *libp2p.Service, codes ...int) multiaddr.Multiaddr { t.Helper() addrs, err := s.Addresses() if err != nil { t.Fatal(err) } - return addrs[0] + + if len(codes) == 0 { + return addrs[0] + } + + for _, addr := range addrs { + for _, proto := range addr.Protocols() { + for _, code := range codes { + if proto.Code == code { + return addr + } + } + } + } + + return nil } diff --git a/pkg/p2p/libp2p/peer.go b/pkg/p2p/libp2p/peer.go index c8eeb1578c2..fd59fe57564 100644 --- a/pkg/p2p/libp2p/peer.go +++ b/pkg/p2p/libp2p/peer.go @@ -119,10 +119,15 @@ func (r *peerRegistry) peers() []p2p.Peer { r.mu.RLock() peers := make([]p2p.Peer, 0, len(r.overlays)) for p, a := range r.overlays { - peers = append(peers, p2p.Peer{ - Address: a, - FullNode: r.full[p], - }) + peer := p2p.Peer{ + Address: a, + FullNode: r.full[p], + P2PTransport: make(map[string]struct{}), + } + for conn := range r.connections[p] { + peer.P2PTransport[conn.ConnState().Transport] = struct{}{} + } + peers = append(peers, peer) } r.mu.RUnlock() sort.Slice(peers, func(i, j int) bool { diff --git a/pkg/p2p/p2p.go b/pkg/p2p/p2p.go index 4f7897be592..c122c3e2104 100644 --- a/pkg/p2p/p2p.go +++ b/pkg/p2p/p2p.go @@ -191,6 +191,7 @@ type Peer struct { Address swarm.Address FullNode bool EthereumAddress []byte + P2PTransport map[string]struct{} } // BlockListedPeer holds information about a Peer that is blocked.