From 12ddb0739ee8c8ac7d732462846e453924afeec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rich=CE=9Brd?= Date: Mon, 6 Sep 2021 09:46:35 -0400 Subject: [PATCH] feat: obtain external address for rendezvous (#2333) * feat: obtain external address for rendezvous If the ext ip returned by geth is 127.0.0.1, it will attempt to obtain the external IP address via rendezvous and use that to register the ens record later * fix: failing test * fix: code review, and adding external ip address to logs --- discovery/rendezvous.go | 62 ++++++++++++++++--- discovery/rendezvous_test.go | 4 +- go.mod | 2 +- go.sum | 4 +- params/config.go | 3 + .../github.com/status-im/rendezvous/client.go | 36 ++++++++++- .../status-im/rendezvous/protocol/protocol.go | 10 +++ .../status-im/rendezvous/server/server.go | 13 +++- vendor/modules.txt | 2 +- 9 files changed, 120 insertions(+), 16 deletions(-) diff --git a/discovery/rendezvous.go b/discovery/rendezvous.go index 0dfc424ab..2583669bf 100644 --- a/discovery/rendezvous.go +++ b/discovery/rendezvous.go @@ -104,7 +104,7 @@ func (r *Rendezvous) Stop() error { return nil } -func (r *Rendezvous) MakeRecord() (record enr.Record, err error) { +func (r *Rendezvous) MakeRecord(srv *ma.Multiaddr) (record enr.Record, err error) { r.recordMu.Lock() defer r.recordMu.Unlock() if r.record != nil { @@ -116,7 +116,27 @@ func (r *Rendezvous) MakeRecord() (record enr.Record, err error) { if r.identity == nil { return record, errIdentityIsNil } - record.Set(enr.IP(r.node.IP())) + + ip := r.node.IP() + if srv != nil && (ip.IsLoopback() || IsPrivate(ip)) { // If AdvertiseAddr is not specified, 127.0.0.1 might be returned + ctx, cancel := context.WithTimeout(r.rootCtx, requestTimeout) + defer cancel() + + ipAddr, err := r.client.RemoteIp(ctx, *srv) + if err != nil { + log.Error("could not obtain the external ip address", "err", err) + } else { + parsedIP := net.ParseIP(ipAddr) + if parsedIP != nil { + ip = parsedIP + log.Info("node's external IP address", "ipAddr", ipAddr) + } else { + log.Error("invalid ip address obtained from rendezvous server", "ipaddr", ipAddr, "err", err) + } + } + } + + record.Set(enr.IP(ip)) record.Set(enr.TCP(r.node.TCP())) record.Set(enr.UDP(r.node.UDP())) // public key is added to ENR when ENR is signed @@ -127,8 +147,7 @@ func (r *Rendezvous) MakeRecord() (record enr.Record, err error) { return record, nil } -func (r *Rendezvous) register(topic string, record enr.Record) error { - srv := r.servers[rand.Intn(len(r.servers))] // nolint: gosec +func (r *Rendezvous) register(srv ma.Multiaddr, topic string, record enr.Record) error { ctx, cancel := context.WithTimeout(r.rootCtx, requestTimeout) defer cancel() @@ -146,7 +165,8 @@ func (r *Rendezvous) register(topic string, record enr.Record) error { // Register renews registration in the specified server. func (r *Rendezvous) Register(topic string, stop chan struct{}) error { - record, err := r.MakeRecord() + srv := r.getRandomServer() + record, err := r.MakeRecord(&srv) if err != nil { return err } @@ -155,7 +175,7 @@ func (r *Rendezvous) Register(topic string, stop chan struct{}) error { ticker := time.NewTicker(r.registrationPeriod / 2) defer ticker.Stop() - if err := r.register(topic, record); err == context.Canceled { + if err := r.register(srv, topic, record); err == context.Canceled { return err } @@ -164,7 +184,7 @@ func (r *Rendezvous) Register(topic string, stop chan struct{}) error { case <-stop: return nil case <-ticker.C: - if err := r.register(topic, record); err == context.Canceled { + if err := r.register(r.getRandomServer(), topic, record); err == context.Canceled { return err } else if err == errDiscoveryIsStopped { return nil @@ -230,6 +250,10 @@ func (r *Rendezvous) Discover( } } +func (r *Rendezvous) getRandomServer() ma.Multiaddr { + return r.servers[rand.Intn(len(r.servers))] // nolint: gosec +} + func enrToNode(record enr.Record) (*discv5.Node, error) { var ( key enode.Secp256k1 @@ -253,3 +277,27 @@ func enrToNode(record enr.Record) (*discv5.Node, error) { _ = record.Load(&uport) return discv5.NewNode(nodeID, net.IP(ip), uint16(uport), uint16(tport)), nil } + +// IsPrivate reports whether ip is a private address, according to +// RFC 1918 (IPv4 addresses) and RFC 4193 (IPv6 addresses). +// Copied/Adapted from https://go-review.googlesource.com/c/go/+/272668/11/src/net/ip.go +// Copyright (c) The Go Authors. All rights reserved. +// @TODO: once Go 1.17 is released in Q42021, remove this function as it will become part of the language +func IsPrivate(ip net.IP) bool { + if ip4 := ip.To4(); ip4 != nil { + // Following RFC 4193, Section 3. Local IPv6 Unicast Addresses which says: + // The Internet Assigned Numbers Authority (IANA) has reserved the + // following three blocks of the IPv4 address space for private internets: + // 10.0.0.0 - 10.255.255.255 (10/8 prefix) + // 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) + // 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) + return ip4[0] == 10 || + (ip4[0] == 172 && ip4[1]&0xf0 == 16) || + (ip4[0] == 192 && ip4[1] == 168) + } + // Following RFC 4193, Section 3. Private Address Space which says: + // The Internet Assigned Numbers Authority (IANA) has reserved the + // following block of the IPv6 address space for local internets: + // FC00:: - FDFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF (FC00::/7 prefix) + return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc +} diff --git a/discovery/rendezvous_test.go b/discovery/rendezvous_test.go index 7251f72b1..43a6621bb 100644 --- a/discovery/rendezvous_test.go +++ b/discovery/rendezvous_test.go @@ -68,7 +68,7 @@ func TestMakeRecordReturnsCachedRecord(t *testing.T) { record := enr.Record{} require.NoError(t, enode.SignV4(&record, identity)) c := NewRendezvousWithENR(nil, record) - rst, err := c.MakeRecord() + rst, err := c.MakeRecord(nil) require.NoError(t, err) require.NotNil(t, enode.V4ID{}.NodeAddr(&rst)) require.Equal(t, enode.V4ID{}.NodeAddr(&record), enode.V4ID{}.NodeAddr(&rst)) @@ -79,7 +79,7 @@ func TestRendezvousRegisterAndDiscoverExitGracefully(t *testing.T) { require.NoError(t, err) require.NoError(t, r.Start()) require.NoError(t, r.Stop()) - require.EqualError(t, errDiscoveryIsStopped, r.register("", enr.Record{}).Error()) + require.EqualError(t, errDiscoveryIsStopped, r.register(r.getRandomServer(), "", enr.Record{}).Error()) _, err = r.discoverRequest(nil, "") require.EqualError(t, errDiscoveryIsStopped, err.Error()) } diff --git a/go.mod b/go.mod index e88a6ad7d..e8f91ec37 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/status-im/go-wakurelay-pubsub v0.4.3-0.20210729162817-adc68830282a github.com/status-im/markdown v0.0.0-20201022101546-c0cbdd5763bf github.com/status-im/migrate/v4 v4.6.2-status.2 - github.com/status-im/rendezvous v1.3.2 + github.com/status-im/rendezvous v1.3.1-0.20210824184947-7c79c858170c github.com/status-im/status-go/extkeys v1.1.2 github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 79f3f1cae..37eec0d45 100644 --- a/go.sum +++ b/go.sum @@ -1113,8 +1113,8 @@ github.com/status-im/markdown v0.0.0-20201022101546-c0cbdd5763bf h1:0w1mjNUqEHoL github.com/status-im/markdown v0.0.0-20201022101546-c0cbdd5763bf/go.mod h1:9yR8woqkJIHs3sf9pEjYvaGfmhsXR1leEMAX6+Z5y+M= github.com/status-im/migrate/v4 v4.6.2-status.2 h1:SdC+sMDl/aI7vUlwD2qj2p7KsK4T60IS9z4/rYCCbI8= github.com/status-im/migrate/v4 v4.6.2-status.2/go.mod h1:c/kc90n47GZu/58nnz1OMLTf7uE4Da4gZP5qmU+A/v8= -github.com/status-im/rendezvous v1.3.2 h1:eLTQ1EEg/qTsFDM6lwIf7tAFqiD3zEHnbWKaS3fvd/E= -github.com/status-im/rendezvous v1.3.2/go.mod h1:CK8B3kCbx3QrE0V64aAocU8oh9KRktoKSU0sqiF6MwI= +github.com/status-im/rendezvous v1.3.1-0.20210824184947-7c79c858170c h1:l83ciN0A5LW7c6NMa/j0x2mH1vsv3hbAAUTEwZw6qF4= +github.com/status-im/rendezvous v1.3.1-0.20210824184947-7c79c858170c/go.mod h1:CK8B3kCbx3QrE0V64aAocU8oh9KRktoKSU0sqiF6MwI= github.com/status-im/resize v0.0.0-20201215164250-7c6d9f0d3088 h1:ClCAP2FPCvl8hGMhbUx/tq/sOu2wibztAa5jAvQEe4Q= github.com/status-im/resize v0.0.0-20201215164250-7c6d9f0d3088/go.mod h1:+92j1tN27DypDeBFxkg0uzkqfh1bNHTZe3Bv2PjvxpM= github.com/status-im/status-go/extkeys v1.1.2 h1:FSjARgDathJ3rIapJt851LsIXP9Oyuu2M2jPJKuzloU= diff --git a/params/config.go b/params/config.go index 3a28a6ae8..a6f516585 100644 --- a/params/config.go +++ b/params/config.go @@ -340,6 +340,9 @@ type NodeConfig struct { // AdvertiseAddr is a public IP address the node wants to be found with. // It is especially useful when using floating IPs attached to a server. + // This configuration value is used by rendezvous protocol, and it's optional + // If no value is specified, it will attempt to determine the node's external + // IP address. A value can be specified in case the returned address is incorrect AdvertiseAddr string // Name sets the instance name of the node. It must not contain the / character. diff --git a/vendor/github.com/status-im/rendezvous/client.go b/vendor/github.com/status-im/rendezvous/client.go index b6edb6e30..217d2ba87 100644 --- a/vendor/github.com/status-im/rendezvous/client.go +++ b/vendor/github.com/status-im/rendezvous/client.go @@ -115,12 +115,46 @@ func (c Client) Discover(ctx context.Context, srv ma.Multiaddr, topic string, li return val.Records, nil } +func (c Client) RemoteIp(ctx context.Context, srv ma.Multiaddr) (value string, err error) { + s, err := c.newStream(ctx, srv) + if err != nil { + return + } + defer s.Close() + + if err = rlp.Encode(s, protocol.REMOTEIP); err != nil { + return + } + + rs := rlp.NewStream(s, 0) + typ, err := rs.Uint() + if err != nil { + return + } + if protocol.MessageType(typ) != protocol.REMOTEIP_RESPONSE { + err = fmt.Errorf("expected %v as response, but got %v", protocol.REMOTEIP_RESPONSE, typ) + return + } + var val protocol.RemoteIpResponse + if err = rs.Decode(&val); err != nil { + return + } + if val.Status != protocol.OK { + err = fmt.Errorf("remoteip request failed. status code %v", val.Status) + return + } + logger.Debug("received response to remoteip request", "status", val.Status, "ip", val.IP) + value = val.IP + + return +} + func (c Client) newStream(ctx context.Context, srv ma.Multiaddr) (rw network.Stream, err error) { pid, err := srv.ValueForProtocol(ethv4.P_ETHv4) if err != nil { return } - peerid, err := peer.IDB58Decode(pid) + peerid, err := peer.Decode(pid) if err != nil { return } diff --git a/vendor/github.com/status-im/rendezvous/protocol/protocol.go b/vendor/github.com/status-im/rendezvous/protocol/protocol.go index 20560c7fd..1549622b4 100644 --- a/vendor/github.com/status-im/rendezvous/protocol/protocol.go +++ b/vendor/github.com/status-im/rendezvous/protocol/protocol.go @@ -14,6 +14,8 @@ const ( REGISTER_RESPONSE DISCOVER DISCOVER_RESPONSE + REMOTEIP + REMOTEIP_RESPONSE OK ResponseStatus = 0 E_INVALID_NAMESPACE ResponseStatus = 100 @@ -46,3 +48,11 @@ type DiscoverResponse struct { Message string Records []enr.Record } + +type RemoteIp struct { +} + +type RemoteIpResponse struct { + Status ResponseStatus + IP string +} diff --git a/vendor/github.com/status-im/rendezvous/server/server.go b/vendor/github.com/status-im/rendezvous/server/server.go index a392833c8..995f598e6 100644 --- a/vendor/github.com/status-im/rendezvous/server/server.go +++ b/vendor/github.com/status-im/rendezvous/server/server.go @@ -16,6 +16,7 @@ import ( "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/network" + "github.com/multiformats/go-multiaddr" ma "github.com/multiformats/go-multiaddr" "github.com/status-im/rendezvous/protocol" ) @@ -132,7 +133,7 @@ func (srv *Server) startListener() error { return } s.SetReadDeadline(time.Now().Add(srv.readTimeout)) - resptype, resp, err := srv.msgParser(protocol.MessageType(typ), rs) + resptype, resp, err := srv.msgParser(s, protocol.MessageType(typ), rs) if err == io.EOF { return } @@ -198,7 +199,7 @@ type Decoder interface { Decode(val interface{}) error } -func (srv *Server) msgParser(typ protocol.MessageType, d Decoder) (resptype protocol.MessageType, resp interface{}, err error) { +func (srv *Server) msgParser(s network.Stream, typ protocol.MessageType, d Decoder) (resptype protocol.MessageType, resp interface{}, err error) { switch typ { case protocol.REGISTER: var msg protocol.Register @@ -229,6 +230,14 @@ func (srv *Server) msgParser(typ protocol.MessageType, d Decoder) (resptype prot metrics.ObserveDiscoveryDuration(time.Since(start).Seconds(), msg.Topic) metrics.ObserveDiscoverSize(float64(len(records)), msg.Topic) return resptype, protocol.DiscoverResponse{Status: protocol.OK, Records: records}, nil + case protocol.REMOTEIP: + resptype = protocol.REMOTEIP_RESPONSE + ip, err := s.Conn().RemoteMultiaddr().ValueForProtocol(multiaddr.P_IP4) + if err != nil { + metrics.CountError("remoteip") + return resptype, protocol.RemoteIpResponse{Status: protocol.E_INTERNAL_ERROR}, err + } + return resptype, protocol.RemoteIpResponse{Status: protocol.OK, IP: ip}, nil default: metrics.CountError("unknown") // don't send the response diff --git a/vendor/modules.txt b/vendor/modules.txt index 72baadbcc..e35f5b246 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -451,7 +451,7 @@ github.com/status-im/migrate/v4/database/postgres github.com/status-im/migrate/v4/database/sqlcipher github.com/status-im/migrate/v4/internal/url github.com/status-im/migrate/v4/source/go_bindata -# github.com/status-im/rendezvous v1.3.2 +# github.com/status-im/rendezvous v1.3.1-0.20210824184947-7c79c858170c github.com/status-im/rendezvous github.com/status-im/rendezvous/protocol github.com/status-im/rendezvous/server