diff --git a/config/config.go b/config/config.go index 02fd99f9a..2f05a4fa0 100644 --- a/config/config.go +++ b/config/config.go @@ -19,7 +19,7 @@ import ( "github.com/pactus-project/pactus/www/grpc" "github.com/pactus-project/pactus/www/http" "github.com/pactus-project/pactus/www/nanomsg" - toml "github.com/pelletier/go-toml" + "github.com/pelletier/go-toml" ) //go:embed example_config.toml @@ -44,6 +44,7 @@ type NodeConfig struct { } func DefaultNodeConfig() *NodeConfig { + // TODO: We should have default config per network: Testnet, Mainnet. return &NodeConfig{ NumValidators: 7, } diff --git a/config/config_test.go b/config/config_test.go index cf211524a..a6909ada8 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -27,6 +27,7 @@ func TestSaveTestnetConfig(t *testing.T) { assert.NoError(t, err) assert.NoError(t, conf.BasicCheck()) + assert.Equal(t, conf.Network.DefaultPort, 21777) } func TestSaveLocalnetConfig(t *testing.T) { @@ -39,6 +40,7 @@ func TestSaveLocalnetConfig(t *testing.T) { assert.NoError(t, conf.BasicCheck()) assert.Empty(t, conf.Network.Listens) assert.Empty(t, conf.Network.RelayAddrs) + assert.Equal(t, conf.Network.DefaultPort, 21777) } func TestLoadFromFile(t *testing.T) { diff --git a/config/example_config.toml b/config/example_config.toml index 4ddafb915..9f69687a9 100644 --- a/config/example_config.toml +++ b/config/example_config.toml @@ -24,6 +24,15 @@ # `network_key` specifies the private key filename to use for node authentication and encryption in the p2p protocol. ## network_key = "network_key" + # `public_address` is a list of addresses that are public and accessible to other nodes. + # If it is empty, the node attempts to automatically detect its public IP addresses. + # It is recommended to set the public address if available. + # Example addresses: + # - "/ip4/1.2.3.4/tcp/21888" + # - "/ip6/2606:4700:4700::1111/udp/21888" + # - "/dnsaddr/my-server.amazonaws.com/tcp/21888" + ## public_address = [] + # `listens` specifies the addresses and ports where the node will listen for incoming connections from other nodes. ## listens = ["/ip4/0.0.0.0/tcp/21888", "/ip6/::/tcp/21888", "/ip4/0.0.0.0/udp/21888/quic-v1", "/ip6/::/udp/21888/quic-v1"] @@ -58,15 +67,17 @@ # Default is false. ## enable_mdns = false - # `enable_metrics` if enabled, it provides network metrics for the Prometheus software. + # `enable_metrics` provides the network metrics for the Prometheus software. # Default is false. ## enable_metrics = false - # `private_network` if enabled, connects to nodes in the private network. + # `force_private_network` forces the connection to nodes within a private network. + # A private network is a computer network that uses private addresses. + # Read more about private networks here: https://en.wikipedia.org/wiki/Private_network # Default is false. - ## private_network = false + ## force_private_network = false - # `bootstrapper` if enabled, it runs the node in bootstrap mode. + # `bootstrapper` runs the node in bootstrap mode. # Default is false. ## bootstrapper = false diff --git a/genesis/genesis.go b/genesis/genesis.go index a1e6b2f6c..b189eb30a 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -200,3 +200,16 @@ func (gen *Genesis) NetworkName() string { return "unknown" } } + +func (gen *Genesis) DefaultPort() int { + switch gen.ChainType() { + case Mainnet: + return 21888 + case Testnet: + return 21777 + case Localnet: + return 21666 + default: + return 0 + } +} diff --git a/genesis/genesis_test.go b/genesis/genesis_test.go index f0d7e81e5..76f94f6c1 100644 --- a/genesis/genesis_test.go +++ b/genesis/genesis_test.go @@ -48,19 +48,21 @@ func TestMarshaling(t *testing.T) { } func TestGenesisTestNet(t *testing.T) { - g := genesis.TestnetGenesis() - assert.Equal(t, len(g.Validators()), 4) - assert.Equal(t, len(g.Accounts()), 1) + gen := genesis.TestnetGenesis() + assert.Equal(t, len(gen.Validators()), 4) + assert.Equal(t, len(gen.Accounts()), 1) - assert.Equal(t, g.Accounts()[crypto.TreasuryAddress].Balance(), int64(21e15)) + assert.Equal(t, gen.Accounts()[crypto.TreasuryAddress].Balance(), int64(21e15)) genTime, _ := time.Parse("2006-01-02", "2023-10-15") expected, _ := hash.FromString("da602b28f75902c35e3bafeb5733a686c94d5508c92aae68cbd9b37d81cfccf4") - assert.Equal(t, g.Hash(), expected) - assert.Equal(t, g.GenesisTime(), genTime) - assert.Equal(t, g.Params().BondInterval, uint32(120)) - assert.Equal(t, g.ChainType(), genesis.Testnet) - assert.Equal(t, g.TotalSupply(), int64(42*1e15)) + assert.Equal(t, gen.Hash(), expected) + assert.Equal(t, gen.GenesisTime(), genTime) + assert.Equal(t, gen.Params().BondInterval, uint32(120)) + assert.Equal(t, gen.ChainType(), genesis.Testnet) + assert.Equal(t, gen.TotalSupply(), int64(42*1e15)) + assert.Equal(t, gen.NetworkName(), "pactus-testnet") + assert.Equal(t, gen.DefaultPort(), 21777) } func TestCheckGenesisAccountAndValidator(t *testing.T) { @@ -85,4 +87,7 @@ func TestCheckGenesisAccountAndValidator(t *testing.T) { for i, val := range gen.Validators() { assert.Equal(t, val.Hash(), vals[i].Hash()) } + + assert.Equal(t, gen.NetworkName(), "pactus-localnet") + assert.Equal(t, gen.DefaultPort(), 21666) } diff --git a/network/config.go b/network/config.go index 8fedbb10f..cbfd9a482 100644 --- a/network/config.go +++ b/network/config.go @@ -8,18 +8,20 @@ import ( ) type Config struct { - NetworkKey string `toml:"network_key"` - Listens []string `toml:"listens"` - RelayAddrs []string `toml:"relay_addresses"` - BootstrapAddrs []string `toml:"bootstrap_addresses"` - MinConns int `toml:"min_connections"` - MaxConns int `toml:"max_connections"` - EnableNAT bool `toml:"enable_nat"` - EnableRelay bool `toml:"enable_relay"` - EnableMdns bool `toml:"enable_mdns"` - EnableMetrics bool `toml:"enable_metrics"` - PrivateNetwork bool `toml:"private_network"` - Bootstrapper bool `toml:"bootstrapper"` + NetworkKey string `toml:"network_key"` + PublicAddress []string `toml:"public_address"` + Listens []string `toml:"listens"` + RelayAddrs []string `toml:"relay_addresses"` + BootstrapAddrs []string `toml:"bootstrap_addresses"` + MinConns int `toml:"min_connections"` + MaxConns int `toml:"max_connections"` + EnableNAT bool `toml:"enable_nat"` + EnableRelay bool `toml:"enable_relay"` + EnableMdns bool `toml:"enable_mdns"` + EnableMetrics bool `toml:"enable_metrics"` + ForcePrivateNetwork bool `toml:"force_private_network"` + Bootstrapper bool `toml:"bootstrapper"` // TODO: detect it automatically + DefaultPort int `toml:"-"` } func DefaultConfig() *Config { @@ -47,16 +49,17 @@ func DefaultConfig() *Config { "/ip4/0.0.0.0/tcp/21888", "/ip6/::/tcp/21888", "/ip4/0.0.0.0/udp/21888/quic-v1", "/ip6/::/udp/21888/quic-v1", }, - RelayAddrs: []string{}, - BootstrapAddrs: bootstrapAddrs, - MinConns: 8, - MaxConns: 16, - EnableNAT: true, - EnableRelay: false, - EnableMdns: false, - EnableMetrics: false, - PrivateNetwork: false, - Bootstrapper: false, + RelayAddrs: []string{}, + BootstrapAddrs: bootstrapAddrs, + MinConns: 8, + MaxConns: 16, + EnableNAT: true, + EnableRelay: false, + EnableMdns: false, + EnableMetrics: false, + ForcePrivateNetwork: false, + Bootstrapper: false, + DefaultPort: 21777, } } @@ -77,6 +80,9 @@ func (conf *Config) BasicCheck() error { return errors.Errorf(errors.ErrInvalidConfig, "at least one relay address should be defined") } } + if err := validateAddresses(conf.PublicAddress); err != nil { + return err + } if err := validateAddresses(conf.RelayAddrs); err != nil { return err } diff --git a/network/config_test.go b/network/config_test.go index c230a810f..005b49537 100644 --- a/network/config_test.go +++ b/network/config_test.go @@ -23,6 +23,10 @@ func TestDefaultConfigCheck(t *testing.T) { conf.Listens = []string{"/ip4"} assert.Error(t, conf.BasicCheck()) + conf = DefaultConfig() + conf.PublicAddress = []string{"/ip4"} + assert.Error(t, conf.BasicCheck()) + conf = DefaultConfig() conf.RelayAddrs = []string{"/ip4"} assert.Error(t, conf.BasicCheck()) diff --git a/network/dht.go b/network/dht.go index b065f5739..8abc242f4 100644 --- a/network/dht.go +++ b/network/dht.go @@ -23,7 +23,7 @@ func newDHTService(ctx context.Context, host lp2phost.Host, protocolID lp2pcore. if conf.Bootstrapper { mode = lp2pdht.ModeServer } - bootstrapAddrs := PeerAddrsToAddrInfo(conf.BootstrapAddrs) + bootstrapAddrs, _ := MakeAddrInfos(conf.BootstrapAddrs) opts := []lp2pdht.Option{ lp2pdht.Mode(mode), lp2pdht.ProtocolPrefix(protocolID), diff --git a/network/gater.go b/network/gater.go new file mode 100644 index 000000000..87fb6a9f7 --- /dev/null +++ b/network/gater.go @@ -0,0 +1,86 @@ +package network + +import ( + lp2pconnmgr "github.com/libp2p/go-libp2p/core/connmgr" + lp2pcontrol "github.com/libp2p/go-libp2p/core/control" + lp2pnetwork "github.com/libp2p/go-libp2p/core/network" + lp2ppeer "github.com/libp2p/go-libp2p/core/peer" + lp2pconngater "github.com/libp2p/go-libp2p/p2p/net/conngater" + "github.com/multiformats/go-multiaddr" + "github.com/pactus-project/pactus/util/logger" +) + +var _ lp2pconnmgr.ConnectionGater = &ConnectionGater{} + +type ConnectionGater struct { + *lp2pconngater.BasicConnectionGater + + logger *logger.SubLogger +} + +func NewConnectionGater(conf *Config) (*ConnectionGater, error) { + connGater, err := lp2pconngater.NewBasicConnectionGater(nil) + if err != nil { + return nil, err + } + + if !conf.ForcePrivateNetwork { + privateSubnets := PrivateSubnets() + for _, sn := range privateSubnets { + err := connGater.BlockSubnet(sn) + if err != nil { + return nil, LibP2PError{Err: err} + } + } + } + + return &ConnectionGater{ + BasicConnectionGater: connGater, + }, nil +} + +func (g *ConnectionGater) SetLogger(log *logger.SubLogger) { + g.logger = log +} + +func (g *ConnectionGater) InterceptPeerDial(p lp2ppeer.ID) bool { + allow := g.BasicConnectionGater.InterceptPeerDial(p) + if !allow { + g.logger.Debug("InterceptPeerDial not allowed", "p") + } + + return allow +} + +func (g *ConnectionGater) InterceptAddrDial(p lp2ppeer.ID, ma multiaddr.Multiaddr) bool { + allow := g.BasicConnectionGater.InterceptAddrDial(p, ma) + if !allow { + g.logger.Debug("InterceptAddrDial not allowed", "p", p, "ma", ma.String()) + } + + return allow +} + +func (g *ConnectionGater) InterceptAccept(cma lp2pnetwork.ConnMultiaddrs) bool { + allow := g.BasicConnectionGater.InterceptAccept(cma) + if !allow { + g.logger.Debug("InterceptAccept not allowed") + } + + return allow +} + +func (g *ConnectionGater) InterceptSecured(dir lp2pnetwork.Direction, p lp2ppeer.ID, + cma lp2pnetwork.ConnMultiaddrs, +) bool { + allow := g.BasicConnectionGater.InterceptSecured(dir, p, cma) + if !allow { + g.logger.Debug("InterceptSecured not allowed", "p", p) + } + + return allow +} + +func (g *ConnectionGater) InterceptUpgraded(con lp2pnetwork.Conn) (bool, lp2pcontrol.DisconnectReason) { + return g.BasicConnectionGater.InterceptUpgraded(con) +} diff --git a/network/gater_test.go b/network/gater_test.go new file mode 100644 index 000000000..1ae2e9d50 --- /dev/null +++ b/network/gater_test.go @@ -0,0 +1 @@ +package network diff --git a/network/mock.go b/network/mock.go index a58b53404..d1ecc8d26 100644 --- a/network/mock.go +++ b/network/mock.go @@ -5,7 +5,7 @@ import ( "io" lp2pcore "github.com/libp2p/go-libp2p/core" - "github.com/libp2p/go-libp2p/core/peer" + lp2ppeer "github.com/libp2p/go-libp2p/core/peer" "github.com/pactus-project/pactus/util/testsuite" ) @@ -21,12 +21,12 @@ type MockNetwork struct { PublishCh chan PublishData EventCh chan Event - ID peer.ID + ID lp2ppeer.ID OtherNets []*MockNetwork SendError error } -func MockingNetwork(ts *testsuite.TestSuite, id peer.ID) *MockNetwork { +func MockingNetwork(ts *testsuite.TestSuite, id lp2ppeer.ID) *MockNetwork { return &MockNetwork{ TestSuite: ts, PublishCh: make(chan PublishData, 100), @@ -55,7 +55,7 @@ func (mock *MockNetwork) JoinConsensusTopic() error { return nil } -func (mock *MockNetwork) SelfID() peer.ID { +func (mock *MockNetwork) SelfID() lp2ppeer.ID { return mock.ID } @@ -78,7 +78,7 @@ func (mock *MockNetwork) Broadcast(data []byte, _ TopicID) error { return nil } -func (mock *MockNetwork) SendToOthers(data []byte, target *peer.ID) { +func (mock *MockNetwork) SendToOthers(data []byte, target *lp2ppeer.ID) { for _, net := range mock.OtherNets { if target == nil { // Broadcast message @@ -99,7 +99,7 @@ func (mock *MockNetwork) SendToOthers(data []byte, target *peer.ID) { } } -func (mock *MockNetwork) CloseConnection(pid peer.ID) { +func (mock *MockNetwork) CloseConnection(pid lp2ppeer.ID) { for i, net := range mock.OtherNets { if net.ID == pid { mock.OtherNets = append(mock.OtherNets[:i], mock.OtherNets[i+1:]...) @@ -107,7 +107,7 @@ func (mock *MockNetwork) CloseConnection(pid peer.ID) { } } -func (mock *MockNetwork) IsClosed(pid peer.ID) bool { +func (mock *MockNetwork) IsClosed(pid lp2ppeer.ID) bool { for _, net := range mock.OtherNets { if net.ID == pid { return false diff --git a/network/network.go b/network/network.go index 35a42d8ed..4f65d4545 100644 --- a/network/network.go +++ b/network/network.go @@ -13,9 +13,8 @@ import ( lp2phost "github.com/libp2p/go-libp2p/core/host" lp2ppeer "github.com/libp2p/go-libp2p/core/peer" lp2prcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" - lp2pconngater "github.com/libp2p/go-libp2p/p2p/net/conngater" lp2pconnmgr "github.com/libp2p/go-libp2p/p2p/net/connmgr" - ma "github.com/multiformats/go-multiaddr" + "github.com/multiformats/go-multiaddr" "github.com/pactus-project/pactus/util" "github.com/pactus-project/pactus/util/logger" "github.com/pactus-project/pactus/version" @@ -37,6 +36,7 @@ type network struct { mdns *mdnsService dht *dhtService peerMgr *peerMgr + connGater *ConnectionGater stream *streamService gossip *gossipService notifee *NotifeeService @@ -88,45 +88,24 @@ func newNetwork(networkName string, conf *Config, opts []lp2p.Option) (*network, return nil, LibP2PError{Err: err} } + rcMgrOpt := []lp2prcmgr.Option{} if conf.EnableMetrics { - lp2prcmgr.MustRegisterWith(prometheus.DefaultRegisterer) - } + str, err := lp2prcmgr.NewStatsTraceReporter() + if err != nil { + return nil, LibP2PError{Err: err} + } - str, err := lp2prcmgr.NewStatsTraceReporter() - if err != nil { - return nil, LibP2PError{Err: err} + lp2prcmgr.MustRegisterWith(prometheus.DefaultRegisterer) + rcMgrOpt = append(rcMgrOpt, lp2prcmgr.WithTraceReporter(str)) + } else { + rcMgrOpt = append(rcMgrOpt, lp2prcmgr.WithMetricsDisabled()) + opts = append(opts, lp2p.DisableMetrics()) } - // - // This is crazy! - // Do they even know how to configure it properly?! - // - maxConns := conf.MaxConns - minConns := conf.MinConns - limit := lp2prcmgr.DefaultLimits - limit.SystemBaseLimit.ConnsInbound = LogScale(maxConns) - limit.SystemBaseLimit.Conns = LogScale(2 * maxConns) - limit.SystemBaseLimit.StreamsInbound = LogScale(maxConns) - limit.SystemBaseLimit.Streams = LogScale(2 * maxConns) - - limit.ServiceLimitIncrease.ConnsInbound = LogScale(minConns) - limit.ServiceLimitIncrease.Conns = LogScale(2 * minConns) - limit.ServiceLimitIncrease.StreamsInbound = LogScale(minConns) - limit.ServiceLimitIncrease.Streams = LogScale(2 * minConns) - - limit.TransientBaseLimit.ConnsInbound = LogScale(maxConns / 2) - limit.TransientBaseLimit.Conns = LogScale(2 * maxConns / 2) - limit.TransientBaseLimit.StreamsInbound = LogScale(maxConns / 2) - limit.TransientBaseLimit.Streams = LogScale(2 * maxConns / 2) - - limit.TransientLimitIncrease.ConnsInbound = LogScale(minConns / 2) - limit.TransientLimitIncrease.Conns = LogScale(2 * minConns / 2) - limit.TransientLimitIncrease.StreamsInbound = LogScale(minConns / 2) - limit.TransientLimitIncrease.Streams = LogScale(2 * minConns / 2) - + limit := MakeScalingLimitConfig(conf.MinConns, conf.MaxConns) resMgr, err := lp2prcmgr.NewResourceManager( lp2prcmgr.NewFixedLimiter(limit.AutoScale()), - lp2prcmgr.WithTraceReporter(str), + rcMgrOpt..., ) if err != nil { return nil, LibP2PError{Err: err} @@ -149,10 +128,6 @@ func newNetwork(networkName string, conf *Config, opts []lp2p.Option) (*network, lp2p.ConnectionManager(connMgr), ) - if !conf.EnableMetrics { - opts = append(opts, lp2p.DisableMetrics()) - } - if conf.EnableNAT { opts = append(opts, lp2p.EnableNATService(), @@ -160,10 +135,10 @@ func newNetwork(networkName string, conf *Config, opts []lp2p.Option) (*network, ) } - relayAddrs := []ma.Multiaddr{} + relayAddrs := []multiaddr.Multiaddr{} if conf.EnableRelay { for _, s := range conf.RelayAddrs { - addr, err := ma.NewMultiaddr(s) + addr, err := multiaddr.NewMultiaddr(s) if err != nil { return nil, LibP2PError{Err: err} } @@ -188,35 +163,44 @@ func newNetwork(networkName string, conf *Config, opts []lp2p.Option) (*network, // TODO: should include relay addresses privateSubnets := PrivateSubnets() - privateFilters := SubnetsToFilters(privateSubnets, ma.ActionDeny) - addrFactory := lp2p.AddrsFactory(func(as []ma.Multiaddr) []ma.Multiaddr { - addrs := []ma.Multiaddr{} - for _, addr := range as { - if conf.PrivateNetwork || !privateFilters.AddrBlocked(addr) { + privateFilters := SubnetsToFilters(privateSubnets, multiaddr.ActionDeny) + publicAddrs, _ := MakeMultiAddrs(conf.PublicAddress) + if len(publicAddrs) == 0 { + addIP := func(detector func() (string, bool)) { + ip, ok := detector() + if ok { + ma, err := IPToMultiAddr(ip, conf.DefaultPort) + if err == nil { + logger.Debug("ip address is private", "ip", ip) + return + } + if !privateFilters.AddrBlocked(ma) { + publicAddrs = append(publicAddrs, ma) + } + } + } + + addIP(DetectPublicIPv4) + addIP(DetectPublicIPv6) + } + + addrFactory := lp2p.AddrsFactory(func(mas []multiaddr.Multiaddr) []multiaddr.Multiaddr { + addrs := []multiaddr.Multiaddr{} + for _, addr := range mas { + if conf.ForcePrivateNetwork || !privateFilters.AddrBlocked(addr) { addrs = append(addrs, addr) - } else { - // TODO: remove me later - logger.Debug("private ip filtered", "ip", conf.PrivateNetwork) } } + addrs = append(addrs, publicAddrs...) return addrs }) + opts = append(opts, addrFactory) - if !conf.PrivateNetwork { - connGater, err := lp2pconngater.NewBasicConnectionGater(nil) - if err != nil { - return nil, LibP2PError{Err: err} - } - for _, sn := range privateSubnets { - err := connGater.BlockSubnet(sn) - if err != nil { - return nil, LibP2PError{Err: err} - } - } - opts = append(opts, lp2p.ConnectionGater(connGater)) + connGater, err := NewConnectionGater(conf) + if err != nil { + return nil, LibP2PError{Err: err} } - - opts = append(opts, addrFactory) + opts = append(opts, lp2p.ConnectionGater(connGater)) host, err := lp2p.New(opts...) if err != nil { @@ -231,16 +215,18 @@ func newNetwork(networkName string, conf *Config, opts []lp2p.Option) (*network, name: networkName, config: conf, host: host, + connGater: connGater, eventChannel: make(chan Event, 100), } n.logger = logger.NewSubLogger("_network", n) + connGater.SetLogger(n.logger) if conf.EnableMdns { n.mdns = newMdnsService(ctx, n.host, n.logger) } - kadProtocolID := lp2pcore.ProtocolID(fmt.Sprintf("/%s/gossip/v1", n.name)) + kadProtocolID := lp2pcore.ProtocolID(fmt.Sprintf("/%s/gossip/v1", n.name)) // TODO: better name? streamProtocolID := lp2pcore.ProtocolID(fmt.Sprintf("/%s/stream/v1", n.name)) n.dht = newDHTService(n.ctx, n.host, kadProtocolID, conf, n.logger) diff --git a/network/network_test.go b/network/network_test.go index 85f0c77a0..c8f9fc310 100644 --- a/network/network_test.go +++ b/network/network_test.go @@ -56,15 +56,15 @@ func makeTestNetwork(t *testing.T, conf *Config, opts []lp2p.Option) *network { func testConfig() *Config { return &Config{ - Listens: []string{}, - NetworkKey: util.TempFilePath(), - BootstrapAddrs: []string{}, - MinConns: 4, - MaxConns: 8, - EnableNAT: false, - EnableRelay: false, - EnableMdns: false, - PrivateNetwork: true, + Listens: []string{}, + NetworkKey: util.TempFilePath(), + BootstrapAddrs: []string{}, + MinConns: 4, + MaxConns: 8, + EnableNAT: false, + EnableRelay: false, + EnableMdns: false, + ForcePrivateNetwork: true, } } @@ -188,6 +188,7 @@ func TestNetwork(t *testing.T) { confM.EnableRelay = true confM.RelayAddrs = relayAddrs confM.BootstrapAddrs = bootstrapAddresses + confM.ForcePrivateNetwork = true confM.Listens = []string{ "/ip4/127.0.0.1/tcp/0", } @@ -202,6 +203,7 @@ func TestNetwork(t *testing.T) { confN.EnableRelay = true confN.RelayAddrs = relayAddrs confN.BootstrapAddrs = bootstrapAddresses + confN.ForcePrivateNetwork = true confN.Listens = []string{ "/ip4/127.0.0.1/tcp/0", } @@ -215,6 +217,7 @@ func TestNetwork(t *testing.T) { confX := testConfig() confX.EnableRelay = false confX.BootstrapAddrs = bootstrapAddresses + confX.ForcePrivateNetwork = true confX.Listens = []string{ "/ip4/127.0.0.1/tcp/0", } diff --git a/network/peermgr.go b/network/peermgr.go index 6af05f63b..51030e0b8 100644 --- a/network/peermgr.go +++ b/network/peermgr.go @@ -37,7 +37,7 @@ type peerMgr struct { func newPeerMgr(ctx context.Context, h lp2phost.Host, dht *lp2pdht.IpfsDHT, conf *Config, log *logger.SubLogger, ) *peerMgr { - bootstrapAddrs := PeerAddrsToAddrInfo(conf.BootstrapAddrs) + bootstrapAddrs, _ := MakeAddrInfos(conf.BootstrapAddrs) b := &peerMgr{ ctx: ctx, bootstrapAddrs: bootstrapAddrs, diff --git a/network/utils.go b/network/utils.go index fe37fa74b..c241439b3 100644 --- a/network/utils.go +++ b/network/utils.go @@ -2,6 +2,7 @@ package network import ( "context" + "fmt" "math/bits" "net" "time" @@ -9,30 +10,64 @@ import ( lp2phost "github.com/libp2p/go-libp2p/core/host" lp2pnetwork "github.com/libp2p/go-libp2p/core/network" lp2ppeer "github.com/libp2p/go-libp2p/core/peer" + lp2prcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" "github.com/multiformats/go-multiaddr" "github.com/pactus-project/pactus/util/logger" ) -// PeerAddrsToAddrInfo converts a slice of string peer addresses -// to AddrInfo. -func PeerAddrsToAddrInfo(addrs []string) []lp2ppeer.AddrInfo { +// MakeMultiAddrs converts a slice of string peer addresses to MultiAddress. +func MakeMultiAddrs(addrs []string) ([]multiaddr.Multiaddr, error) { + mas := make([]multiaddr.Multiaddr, 0, len(addrs)) + for _, addr := range addrs { + ma, err := multiaddr.NewMultiaddr(addr) + if err != nil { + return nil, err + } + mas = append(mas, ma) + } + return mas, nil +} + +// MakeAddrInfos converts a slice of string peer addresses to AddrInfo. +func MakeAddrInfos(addrs []string) ([]lp2ppeer.AddrInfo, error) { pis := make([]lp2ppeer.AddrInfo, 0, len(addrs)) for _, addr := range addrs { - pinfo, _ := MakeAddressInfo(addr) - if pinfo != nil { - pis = append(pis, *pinfo) + pinfo, err := MakeAddressInfo(addr) + if err != nil { + return nil, err } + pis = append(pis, *pinfo) } - return pis + return pis, nil } // MakeAddressInfo from Multi-address string. func MakeAddressInfo(addr string) (*lp2ppeer.AddrInfo, error) { - maddr, err := multiaddr.NewMultiaddr(addr) + ma, err := multiaddr.NewMultiaddr(addr) + if err != nil { + return nil, err + } + return lp2ppeer.AddrInfoFromP2pAddr(ma) +} + +func IPToMultiAddr(ip string, port int) (multiaddr.Multiaddr, error) { + ipParsed := net.ParseIP(ip) + if ipParsed == nil { + return nil, fmt.Errorf("invalid IP address: %s", ip) + } + + var addr string + if ipParsed.To4() != nil { + addr = fmt.Sprintf("/ip4/%s/tcp/%d", ip, port) + } else { + addr = fmt.Sprintf("/ip6/%s/tcp/%d", ip, port) + } + ma, err := multiaddr.NewMultiaddr(addr) if err != nil { return nil, err } - return lp2ppeer.AddrInfoFromP2pAddr(maddr) + + return ma, nil } // HasPID checks if a peer ID exists in a list of peer IDs. @@ -60,8 +95,10 @@ func ConnectAsync(ctx context.Context, h lp2phost.Host, addrInfo lp2ppeer.AddrIn }() } +// LogScale computes 2^⌈log₂(val)⌉, where ⌈x⌉ represents the ceiling of x. +// For more information, refer to: https://en.wikipedia.org/wiki/Logarithmic_scale func LogScale(val int) int { - bitlen := bits.Len(uint(val)) + bitlen := bits.Len(uint(val - 1)) return 1 << bitlen } @@ -107,3 +144,75 @@ func SubnetsToFilters(subnets []*net.IPNet, action multiaddr.Action) *multiaddr. return filters } + +func MakeScalingLimitConfig(minConns, maxConns int) lp2prcmgr.ScalingLimitConfig { + limit := lp2prcmgr.DefaultLimits + + limit.SystemBaseLimit.ConnsInbound = LogScale(maxConns) + limit.SystemBaseLimit.Conns = LogScale(2 * maxConns) + limit.SystemBaseLimit.StreamsInbound = LogScale(maxConns) + limit.SystemBaseLimit.Streams = LogScale(2 * maxConns) + + limit.ServiceLimitIncrease.ConnsInbound = LogScale(minConns) + limit.ServiceLimitIncrease.Conns = LogScale(2 * minConns) + limit.ServiceLimitIncrease.StreamsInbound = LogScale(minConns) + limit.ServiceLimitIncrease.Streams = LogScale(2 * minConns) + + limit.TransientBaseLimit.ConnsInbound = LogScale(maxConns / 2) + limit.TransientBaseLimit.Conns = LogScale(2 * maxConns / 2) + limit.TransientBaseLimit.StreamsInbound = LogScale(maxConns / 2) + limit.TransientBaseLimit.Streams = LogScale(2 * maxConns / 2) + + limit.TransientLimitIncrease.ConnsInbound = LogScale(minConns / 2) + limit.TransientLimitIncrease.Conns = LogScale(2 * minConns / 2) + limit.TransientLimitIncrease.StreamsInbound = LogScale(minConns / 2) + limit.TransientLimitIncrease.Streams = LogScale(2 * minConns / 2) + + return limit +} + +// DetectPublicIPv4 returns the public IPv4 address of the local machine by dialing a list of specific IP addresses. +func DetectPublicIPv4() (string, bool) { + ips := []string{ + "8.8.8.8", // Google + "1.1.1.1", // Cloudflare + "9.9.9.9", // Quad9 + "208.67.222.222", // OpenDNS + } + + for _, ip := range ips { + conn, err := net.Dial("udp", ip+":53") // Use a UDP connection to avoid blocking + if err == nil { + conn.Close() + + localAddr := conn.LocalAddr().(*net.UDPAddr) + return localAddr.IP.String(), true + } + } + + // If all attempts failed, return false + return "", false +} + +// DetectPublicIPv6 returns the public IPv6 address of the local machine by dialing a list of specific IPv6 addresses. +func DetectPublicIPv6() (string, bool) { + ips := []string{ + "2001:4860:4860::8888", // Google + "2620:0:ccc::2", // OpenDNS + "2620:fe::fe", // Quad9 + "2606:4700:4700::1111", // Cloudflare + } + + for _, ip := range ips { + conn, err := net.Dial("udp", "["+ip+"]:53") // Use a UDP connection to avoid blocking + if err == nil { + conn.Close() + + localAddr := conn.LocalAddr().(*net.UDPAddr) + return localAddr.IP.String(), true + } + } + + // If all attempts failed, return false + return "", false +} diff --git a/network/utils_test.go b/network/utils_test.go new file mode 100644 index 000000000..b5be150f7 --- /dev/null +++ b/network/utils_test.go @@ -0,0 +1,201 @@ +package network + +import ( + "fmt" + "testing" + + lp2ppeer "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/assert" +) + +func TestMakeMultiAddrs(t *testing.T) { + testCases := []struct { + name string + inputAddrs []string + expected []multiaddr.Multiaddr + }{ + { + inputAddrs: []string{ + "/ip4/127.0.0.1/tcp/1234", + "/ip6/::1/tcp/5678/", + "/dns4/example.com", + }, + expected: []multiaddr.Multiaddr{ + multiaddr.Cast([]byte{0x04, 0x7f, 0x00, 0x00, 0x01, 0x06, 0x04, 0xd2}), + multiaddr.Cast([]byte{ + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x16, 0x2e, + }), + multiaddr.Cast([]byte{0x36, 0x0b, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d}), + }, + }, + { + inputAddrs: []string{ + "invalid_address", + }, + expected: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actualPis, actualError := MakeMultiAddrs(tc.inputAddrs) + + if tc.expected != nil { + assert.Equal(t, actualPis, tc.expected) + assert.NoError(t, actualError) + } else { + assert.Error(t, actualError) + assert.Nil(t, actualPis) + } + }) + } +} + +func TestMakeAddrInfos(t *testing.T) { + pid, _ := lp2ppeer.Decode("12D3KooWCwQZt8UriVXobQHPXPR8m83eceXVoeT6brPNiBHomebc") + testCases := []struct { + name string + inputAddrs []string + expectedPis []lp2ppeer.AddrInfo + }{ + { + inputAddrs: []string{ + "/ip4/127.0.0.1/tcp/1234/p2p/12D3KooWCwQZt8UriVXobQHPXPR8m83eceXVoeT6brPNiBHomebc", + "/ip6/::1/tcp/5678/p2p/12D3KooWCwQZt8UriVXobQHPXPR8m83eceXVoeT6brPNiBHomebc", + "/dns4/example.com/tcp/4001/p2p/12D3KooWCwQZt8UriVXobQHPXPR8m83eceXVoeT6brPNiBHomebc", + }, + expectedPis: []lp2ppeer.AddrInfo{ + { + ID: pid, + Addrs: []multiaddr.Multiaddr{ + multiaddr.StringCast("/ip4/127.0.0.1/tcp/1234"), + }, + }, + { + ID: pid, + Addrs: []multiaddr.Multiaddr{ + multiaddr.StringCast("/ip6/::1/tcp/5678"), + }, + }, + { + ID: pid, + Addrs: []multiaddr.Multiaddr{ + multiaddr.StringCast("/dns4/example.com/tcp/4001"), + }, + }, + }, + }, + { + inputAddrs: []string{ + "/ip4/127.0.0.1/tcp/1234", // No peer id + }, + expectedPis: nil, + }, + { + inputAddrs: []string{ + "invalid_address", + }, + expectedPis: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actualPis, actualError := MakeAddrInfos(tc.inputAddrs) + + if tc.expectedPis != nil { + assert.Equal(t, actualPis, tc.expectedPis) + assert.NoError(t, actualError) + } else { + assert.Error(t, actualError) + assert.Nil(t, actualPis) + } + }) + } +} + +func TestIPToMultiAddr(t *testing.T) { + testCases := []struct { + ip string + port int + expected string + }{ + {"127.0.0.1", 8080, "/ip4/127.0.0.1/tcp/8080"}, + {"192.168.1.1", 1234, "/ip4/192.168.1.1/tcp/1234"}, + {"::1", 80, "/ip6/::1/tcp/80"}, + {"invalid_ip", 80, ""}, + } + + for _, testCase := range testCases { + t.Run(testCase.expected, func(t *testing.T) { + ma, err := IPToMultiAddr(testCase.ip, testCase.port) + if testCase.expected != "" { + assert.NoError(t, err) + assert.Equal(t, testCase.expected, ma.String()) + } else { + assert.Error(t, err) + } + }) + } +} + +func TestLogScale(t *testing.T) { + testCases := []struct { + input int + expected int + }{ + {1, 1}, + {2, 2}, + {3, 4}, + {7, 8}, + {8, 8}, + } + + for _, testCase := range testCases { + result := LogScale(testCase.input) + assert.Equal(t, testCase.expected, result, "LogScale(%d) failed", testCase.input) + } +} + +func TestHasPID(t *testing.T) { + pids := []lp2ppeer.ID{"peer1", "peer2", "peer3"} + + assert.True(t, HasPID(pids, lp2ppeer.ID("peer1"))) + assert.False(t, HasPID(pids, lp2ppeer.ID("peer4"))) +} + +func TestSubnetsToFilters(t *testing.T) { + sns := PrivateSubnets() + f := SubnetsToFilters(sns, multiaddr.ActionDeny) + + ma1, _ := multiaddr.NewMultiaddr("/ip4/0.0.0.0") + ma2, _ := multiaddr.NewMultiaddr("/ip4/127.0.0.1") + ma3, _ := multiaddr.NewMultiaddr("/ip4/8.8.8.8") + + assert.False(t, f.AddrBlocked(ma1)) + assert.True(t, f.AddrBlocked(ma2)) + assert.False(t, f.AddrBlocked(ma3)) +} + +func TestDetectPublicIPv4(t *testing.T) { + ip, ok := DetectPublicIPv4() + assert.True(t, ok) + assert.NotNil(t, ip) + + fmt.Println(ip) +} + +func TestDetectPublicIPv6(t *testing.T) { + ip, ok := DetectPublicIPv6() + if ok { + assert.NotNil(t, ip) + + fmt.Println(ip) + ma, _ := IPToMultiAddr(ip, 1234) + privateSubnets := PrivateSubnets() + privateFilters := SubnetsToFilters(privateSubnets, multiaddr.ActionDeny) + assert.False(t, privateFilters.AddrBlocked(ma)) + } +} diff --git a/tests/main_test.go b/tests/main_test.go index e9020d357..6dc59f32a 100644 --- a/tests/main_test.go +++ b/tests/main_test.go @@ -82,7 +82,7 @@ func TestMain(m *testing.M) { tConfigs[i].Sync.NodeNetwork = false tConfigs[i].Sync.Firewall.Enabled = false tConfigs[i].Network.EnableMdns = true - tConfigs[i].Network.PrivateNetwork = true + tConfigs[i].Network.ForcePrivateNetwork = true tConfigs[i].Network.Bootstrapper = true tConfigs[i].Network.NetworkKey = util.TempFilePath() tConfigs[i].Network.Listens = []string{"/ip4/127.0.0.1/tcp/0", "/ip4/127.0.0.1/udp/0/quic-v1"}