diff --git a/cmd/waku/flags.go b/cmd/waku/flags.go index f40c67a9..7c8c2c6b 100644 --- a/cmd/waku/flags.go +++ b/cmd/waku/flags.go @@ -147,11 +147,13 @@ var ( Destination: &options.NAT, // TODO: accept none,any,upnp,extaddr EnvVars: []string{"WAKUNODE2_NAT"}, }) - AdvertiseAddress = altsrc.NewStringFlag(&cli.StringFlag{ - Name: "advertise-address", - Usage: "External address to advertise to other nodes (overrides --address and --ws-address flags)", - Destination: &options.AdvertiseAddress, - EnvVars: []string{"WAKUNODE2_ADVERTISE_ADDRESS"}, + AdvertiseAddress = cliutils.NewGenericFlagMultiValue(&cli.GenericFlag{ + Name: "ext-multiaddr", + Usage: "External address to advertise to other nodes. Ooverrides --address and --ws-address flags. Option may be repeated", + Value: &cliutils.MultiaddrSlice{ + Values: &options.AdvertiseAddresses, + }, + EnvVars: []string{"WAKUNODE2_EXT_MULTIADDR"}, }) ShowAddresses = altsrc.NewBoolFlag(&cli.BoolFlag{ Name: "show-addresses", diff --git a/waku/node.go b/waku/node.go index 7d5bd5a2..b393af9f 100644 --- a/waku/node.go +++ b/waku/node.go @@ -56,26 +56,6 @@ func failOnErr(err error, msg string) { } } -func freePort() (int, error) { - addr, err := net.ResolveTCPAddr("tcp", "localhost:0") - if err != nil { - return 0, err - } - - l, err := net.ListenTCP("tcp", addr) - if err != nil { - return 0, err - } - - port := l.Addr().(*net.TCPAddr).Port - err = l.Close() - if err != nil { - return 0, err - } - - return port, nil -} - const dialTimeout = 7 * time.Second // Execute starts a go-waku node with settings determined by the Options parameter @@ -127,21 +107,8 @@ func Execute(options Options) { node.WithKeepAlive(options.KeepAlive), } - if options.AdvertiseAddress != "" { - advertiseAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", options.AdvertiseAddress, options.Port)) - failOnErr(err, "Invalid advertise address") - - if advertiseAddr.Port == 0 { - for { - p, err := freePort() - if err == nil { - advertiseAddr.Port = p - break - } - } - } - - nodeOpts = append(nodeOpts, node.WithAdvertiseAddress(advertiseAddr)) + if len(options.AdvertiseAddresses) != 0 { + nodeOpts = append(nodeOpts, node.WithAdvertiseAddresses(options.AdvertiseAddresses...)) } if options.Dns4DomainName != "" { @@ -149,7 +116,7 @@ func Execute(options Options) { } libp2pOpts := node.DefaultLibP2POptions - if options.AdvertiseAddress == "" { + if len(options.AdvertiseAddresses) == 0 { libp2pOpts = append(libp2pOpts, libp2p.NATPortMap()) // Attempt to open ports using uPNP for NATed hosts.) } diff --git a/waku/options.go b/waku/options.go index 142d4596..d07b02e6 100644 --- a/waku/options.go +++ b/waku/options.go @@ -141,25 +141,25 @@ type PeerExchangeOptions struct { // Options contains all the available features and settings that can be // configured via flags when executing go-waku as a service. type Options struct { - Port int - Address string - Dns4DomainName string - NodeKey *ecdsa.PrivateKey - KeyFile string - KeyPasswd string - GenerateKey bool - Overwrite bool - StaticNodes []multiaddr.Multiaddr - KeepAlive time.Duration - AdvertiseAddress string - ShowAddresses bool - LogLevel string - LogEncoding string - LogOutput string - NAT string - PersistPeers bool - UserAgent string - PProf bool + Port int + Address string + Dns4DomainName string + NodeKey *ecdsa.PrivateKey + KeyFile string + KeyPasswd string + GenerateKey bool + Overwrite bool + StaticNodes []multiaddr.Multiaddr + KeepAlive time.Duration + AdvertiseAddresses []multiaddr.Multiaddr + ShowAddresses bool + LogLevel string + LogEncoding string + LogOutput string + NAT string + PersistPeers bool + UserAgent string + PProf bool PeerExchange PeerExchangeOptions Websocket WSOptions diff --git a/waku/v2/discv5/discover.go b/waku/v2/discv5/discover.go index ec23f6f3..c4f0c04b 100644 --- a/waku/v2/discv5/discover.go +++ b/waku/v2/discv5/discover.go @@ -11,6 +11,7 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multiaddr" "github.com/waku-org/go-discover/discover" "github.com/waku-org/go-waku/logging" "github.com/waku-org/go-waku/waku/v2/utils" @@ -22,7 +23,6 @@ import ( var ErrNoDiscV5Listener = errors.New("no discv5 listener") - type DiscoveryV5 struct { sync.RWMutex @@ -46,7 +46,7 @@ type discV5Parameters struct { autoUpdate bool bootnodes []*enode.Node udpPort uint - advertiseAddr *net.IP + advertiseAddr []multiaddr.Multiaddr } type DiscoveryV5Option func(*discV5Parameters) @@ -65,9 +65,9 @@ func WithBootnodes(bootnodes []*enode.Node) DiscoveryV5Option { } } -func WithAdvertiseAddr(addr net.IP) DiscoveryV5Option { +func WithAdvertiseAddr(addr []multiaddr.Multiaddr) DiscoveryV5Option { return func(params *discV5Parameters) { - params.advertiseAddr = &addr + params.advertiseAddr = addr } } diff --git a/waku/v2/node/localnode.go b/waku/v2/node/localnode.go index b602897b..941688b2 100644 --- a/waku/v2/node/localnode.go +++ b/waku/v2/node/localnode.go @@ -53,7 +53,7 @@ func writeMultiaddressField(localnode *enode.LocalNode, addrAggr []ma.Multiaddr) return nil } -func (w *WakuNode) updateLocalNode(localnode *enode.LocalNode, multiaddrs []ma.Multiaddr, ipAddr *net.TCPAddr, udpPort uint, wakuFlags utils.WakuEnrBitfield, advertiseAddr *net.IP, shouldAutoUpdate bool, log *zap.Logger) error { +func (w *WakuNode) updateLocalNode(localnode *enode.LocalNode, multiaddrs []ma.Multiaddr, ipAddr *net.TCPAddr, udpPort uint, wakuFlags utils.WakuEnrBitfield, advertiseAddr []ma.Multiaddr, shouldAutoUpdate bool, log *zap.Logger) error { localnode.SetFallbackUDP(int(udpPort)) localnode.Set(enr.WithEntry(utils.WakuENRField, wakuFlags)) localnode.SetFallbackIP(net.IP{127, 0, 0, 1}) @@ -65,13 +65,22 @@ func (w *WakuNode) updateLocalNode(localnode *enode.LocalNode, multiaddrs []ma.M if advertiseAddr != nil { // An advertised address disables libp2p address updates // and discv5 predictions - localnode.SetStaticIP(*advertiseAddr) + + ipAddr, err := selectMostExternalAddress(advertiseAddr) + if err != nil { + return err + } + + localnode.SetStaticIP(ipAddr.IP) localnode.Set(enr.TCP(uint16(ipAddr.Port))) // TODO: ipv6? + + return writeMultiaddresses(localnode, multiaddrs) } else if !shouldAutoUpdate { // We received a libp2p address update. Autoupdate is disabled // Using a static ip will disable endpoint prediction. localnode.SetStaticIP(ipAddr.IP) localnode.Set(enr.TCP(uint16(ipAddr.Port))) // TODO: ipv6? + return writeMultiaddresses(localnode, multiaddrs) } else { // We received a libp2p address update, but we should still // allow discv5 to update the enr record. We set the localnode @@ -94,8 +103,13 @@ func (w *WakuNode) updateLocalNode(localnode *enode.LocalNode, multiaddrs []ma.M localnode.Delete(enr.IPv6{}) localnode.Delete(enr.TCP6(0)) } + + return writeMultiaddresses(localnode, multiaddrs) } +} + +func writeMultiaddresses(localnode *enode.LocalNode, multiaddrs []ma.Multiaddr) error { // Randomly shuffle multiaddresses rand.Seed(time.Now().UnixNano()) rand.Shuffle(len(multiaddrs), func(i, j int) { multiaddrs[i], multiaddrs[j] = multiaddrs[j], multiaddrs[i] }) @@ -203,7 +217,6 @@ func extractIPAddressForENR(addr ma.Multiaddr) (*net.TCPAddr, error) { func selectMostExternalAddress(addresses []ma.Multiaddr) (*net.TCPAddr, error) { var ipAddrs []*net.TCPAddr - for _, addr := range addresses { ipAddr, err := extractIPAddressForENR(addr) if err != nil { @@ -337,7 +350,7 @@ func (w *WakuNode) setupENR(ctx context.Context, addrs []ma.Multiaddr) error { return err } - err = w.updateLocalNode(w.localNode, multiaddresses, ipAddr, w.opts.udpPort, w.wakuFlag, w.opts.advertiseAddr, w.opts.discV5autoUpdate, w.log) + err = w.updateLocalNode(w.localNode, multiaddresses, ipAddr, w.opts.udpPort, w.wakuFlag, w.opts.advertiseAddrs, w.opts.discV5autoUpdate, w.log) if err != nil { w.log.Error("updating localnode ENR record", zap.Error(err)) return err diff --git a/waku/v2/node/wakunode2.go b/waku/v2/node/wakunode2.go index 1b0ad082..57eadb9c 100644 --- a/waku/v2/node/wakunode2.go +++ b/waku/v2/node/wakunode2.go @@ -554,8 +554,8 @@ func (w *WakuNode) mountDiscV5() error { discv5.WithAutoUpdate(w.opts.discV5autoUpdate), } - if w.opts.advertiseAddr != nil { - discV5Options = append(discV5Options, discv5.WithAdvertiseAddr(*w.opts.advertiseAddr)) + if w.opts.advertiseAddrs != nil { + discV5Options = append(discV5Options, discv5.WithAdvertiseAddr(w.opts.advertiseAddrs)) } var err error diff --git a/waku/v2/node/wakuoptions.go b/waku/v2/node/wakuoptions.go index ceffd7c0..2c201d00 100644 --- a/waku/v2/node/wakuoptions.go +++ b/waku/v2/node/wakuoptions.go @@ -23,6 +23,7 @@ import ( quic "github.com/libp2p/go-libp2p/p2p/transport/quic" "github.com/libp2p/go-libp2p/p2p/transport/tcp" "github.com/multiformats/go-multiaddr" + ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr/net" "github.com/waku-org/go-waku/waku/v2/protocol/filter" "github.com/waku-org/go-waku/waku/v2/protocol/pb" @@ -42,7 +43,7 @@ const defaultMinRelayPeersToPublish = 0 type WakuNodeParameters struct { hostAddr *net.TCPAddr dns4Domain string - advertiseAddr *net.IP + advertiseAddrs []multiaddr.Multiaddr multiAddr []multiaddr.Multiaddr addressFactory basichost.AddrsFactory privKey *ecdsa.PrivateKey @@ -199,32 +200,12 @@ func WithHostAddress(hostAddr *net.TCPAddr) WakuNodeOption { } } -// WithAdvertiseAddress is a WakuNodeOption that allows overriding the address used in the waku node with custom value -func WithAdvertiseAddress(address *net.TCPAddr) WakuNodeOption { +// WithAdvertiseAddresses is a WakuNodeOption that allows overriding the address used in the waku node with custom value +func WithAdvertiseAddresses(advertiseAddrs ...ma.Multiaddr) WakuNodeOption { return func(params *WakuNodeParameters) error { - params.advertiseAddr = &address.IP - - advertiseAddress, err := manet.FromNetAddr(address) - if err != nil { - return err - } - + params.advertiseAddrs = advertiseAddrs params.addressFactory = func([]multiaddr.Multiaddr) (addresses []multiaddr.Multiaddr) { - addresses = append(addresses, advertiseAddress) - if params.enableWSS { - wsMa, err := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%d/wss", address.IP, params.wssPort)) - if err != nil { - panic(err) - } - addresses = append(addresses, wsMa) - } else if params.enableWS { - wsMa, err := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%d/ws", address.IP, params.wsPort)) - if err != nil { - panic(err) - } - addresses = append(addresses, wsMa) - } - return addresses + return advertiseAddrs } return nil } diff --git a/waku/v2/node/wakuoptions_test.go b/waku/v2/node/wakuoptions_test.go index 26082cdb..f0f70bd7 100644 --- a/waku/v2/node/wakuoptions_test.go +++ b/waku/v2/node/wakuoptions_test.go @@ -27,15 +27,13 @@ func TestWakuOptions(t *testing.T) { addr, err := multiaddr.NewMultiaddr("/ip4/0.0.0.0/tcp/4000/ws") require.NoError(t, err) - advertiseAddr, _ := net.ResolveTCPAddr("tcp", "0.0.0.0:0") - storeFactory := func(w *WakuNode) store.Store { return store.NewWakuStore(w.host, w.swap, w.opts.messageProvider, w.timesource, w.log) } options := []WakuNodeOption{ WithHostAddress(hostAddr), - WithAdvertiseAddress(advertiseAddr), + WithAdvertiseAddresses(addr), WithMultiaddress([]multiaddr.Multiaddr{addr}), WithPrivateKey(prvKey), WithLibP2POptions(),