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
This commit is contained in:
RichΛrd 2021-09-06 09:46:35 -04:00 committed by GitHub
parent 012e74fd79
commit 12ddb0739e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 120 additions and 16 deletions

View File

@ -104,7 +104,7 @@ func (r *Rendezvous) Stop() error {
return nil 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() r.recordMu.Lock()
defer r.recordMu.Unlock() defer r.recordMu.Unlock()
if r.record != nil { if r.record != nil {
@ -116,7 +116,27 @@ func (r *Rendezvous) MakeRecord() (record enr.Record, err error) {
if r.identity == nil { if r.identity == nil {
return record, errIdentityIsNil 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.TCP(r.node.TCP()))
record.Set(enr.UDP(r.node.UDP())) record.Set(enr.UDP(r.node.UDP()))
// public key is added to ENR when ENR is signed // 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 return record, nil
} }
func (r *Rendezvous) register(topic string, record enr.Record) error { func (r *Rendezvous) register(srv ma.Multiaddr, topic string, record enr.Record) error {
srv := r.servers[rand.Intn(len(r.servers))] // nolint: gosec
ctx, cancel := context.WithTimeout(r.rootCtx, requestTimeout) ctx, cancel := context.WithTimeout(r.rootCtx, requestTimeout)
defer cancel() defer cancel()
@ -146,7 +165,8 @@ func (r *Rendezvous) register(topic string, record enr.Record) error {
// Register renews registration in the specified server. // Register renews registration in the specified server.
func (r *Rendezvous) Register(topic string, stop chan struct{}) error { 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 { if err != nil {
return err return err
} }
@ -155,7 +175,7 @@ func (r *Rendezvous) Register(topic string, stop chan struct{}) error {
ticker := time.NewTicker(r.registrationPeriod / 2) ticker := time.NewTicker(r.registrationPeriod / 2)
defer ticker.Stop() defer ticker.Stop()
if err := r.register(topic, record); err == context.Canceled { if err := r.register(srv, topic, record); err == context.Canceled {
return err return err
} }
@ -164,7 +184,7 @@ func (r *Rendezvous) Register(topic string, stop chan struct{}) error {
case <-stop: case <-stop:
return nil return nil
case <-ticker.C: 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 return err
} else if err == errDiscoveryIsStopped { } else if err == errDiscoveryIsStopped {
return nil 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) { func enrToNode(record enr.Record) (*discv5.Node, error) {
var ( var (
key enode.Secp256k1 key enode.Secp256k1
@ -253,3 +277,27 @@ func enrToNode(record enr.Record) (*discv5.Node, error) {
_ = record.Load(&uport) _ = record.Load(&uport)
return discv5.NewNode(nodeID, net.IP(ip), uint16(uport), uint16(tport)), nil 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
}

View File

@ -68,7 +68,7 @@ func TestMakeRecordReturnsCachedRecord(t *testing.T) {
record := enr.Record{} record := enr.Record{}
require.NoError(t, enode.SignV4(&record, identity)) require.NoError(t, enode.SignV4(&record, identity))
c := NewRendezvousWithENR(nil, record) c := NewRendezvousWithENR(nil, record)
rst, err := c.MakeRecord() rst, err := c.MakeRecord(nil)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, enode.V4ID{}.NodeAddr(&rst)) require.NotNil(t, enode.V4ID{}.NodeAddr(&rst))
require.Equal(t, enode.V4ID{}.NodeAddr(&record), 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, err)
require.NoError(t, r.Start()) require.NoError(t, r.Start())
require.NoError(t, r.Stop()) 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, "") _, err = r.discoverRequest(nil, "")
require.EqualError(t, errDiscoveryIsStopped, err.Error()) require.EqualError(t, errDiscoveryIsStopped, err.Error())
} }

2
go.mod
View File

@ -51,7 +51,7 @@ require (
github.com/status-im/go-wakurelay-pubsub v0.4.3-0.20210729162817-adc68830282a 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/markdown v0.0.0-20201022101546-c0cbdd5763bf
github.com/status-im/migrate/v4 v4.6.2-status.2 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/status-go/extkeys v1.1.2
github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501 github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0

4
go.sum
View File

@ -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/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 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/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.1-0.20210824184947-7c79c858170c h1:l83ciN0A5LW7c6NMa/j0x2mH1vsv3hbAAUTEwZw6qF4=
github.com/status-im/rendezvous v1.3.2/go.mod h1:CK8B3kCbx3QrE0V64aAocU8oh9KRktoKSU0sqiF6MwI= 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 h1:ClCAP2FPCvl8hGMhbUx/tq/sOu2wibztAa5jAvQEe4Q=
github.com/status-im/resize v0.0.0-20201215164250-7c6d9f0d3088/go.mod h1:+92j1tN27DypDeBFxkg0uzkqfh1bNHTZe3Bv2PjvxpM= 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= github.com/status-im/status-go/extkeys v1.1.2 h1:FSjARgDathJ3rIapJt851LsIXP9Oyuu2M2jPJKuzloU=

View File

@ -340,6 +340,9 @@ type NodeConfig struct {
// AdvertiseAddr is a public IP address the node wants to be found with. // 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. // 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 AdvertiseAddr string
// Name sets the instance name of the node. It must not contain the / character. // Name sets the instance name of the node. It must not contain the / character.

View File

@ -115,12 +115,46 @@ func (c Client) Discover(ctx context.Context, srv ma.Multiaddr, topic string, li
return val.Records, nil 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) { func (c Client) newStream(ctx context.Context, srv ma.Multiaddr) (rw network.Stream, err error) {
pid, err := srv.ValueForProtocol(ethv4.P_ETHv4) pid, err := srv.ValueForProtocol(ethv4.P_ETHv4)
if err != nil { if err != nil {
return return
} }
peerid, err := peer.IDB58Decode(pid) peerid, err := peer.Decode(pid)
if err != nil { if err != nil {
return return
} }

View File

@ -14,6 +14,8 @@ const (
REGISTER_RESPONSE REGISTER_RESPONSE
DISCOVER DISCOVER
DISCOVER_RESPONSE DISCOVER_RESPONSE
REMOTEIP
REMOTEIP_RESPONSE
OK ResponseStatus = 0 OK ResponseStatus = 0
E_INVALID_NAMESPACE ResponseStatus = 100 E_INVALID_NAMESPACE ResponseStatus = 100
@ -46,3 +48,11 @@ type DiscoverResponse struct {
Message string Message string
Records []enr.Record Records []enr.Record
} }
type RemoteIp struct {
}
type RemoteIpResponse struct {
Status ResponseStatus
IP string
}

View File

@ -16,6 +16,7 @@ import (
"github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/network"
"github.com/multiformats/go-multiaddr"
ma "github.com/multiformats/go-multiaddr" ma "github.com/multiformats/go-multiaddr"
"github.com/status-im/rendezvous/protocol" "github.com/status-im/rendezvous/protocol"
) )
@ -132,7 +133,7 @@ func (srv *Server) startListener() error {
return return
} }
s.SetReadDeadline(time.Now().Add(srv.readTimeout)) 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 { if err == io.EOF {
return return
} }
@ -198,7 +199,7 @@ type Decoder interface {
Decode(val interface{}) error 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 { switch typ {
case protocol.REGISTER: case protocol.REGISTER:
var msg 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.ObserveDiscoveryDuration(time.Since(start).Seconds(), msg.Topic)
metrics.ObserveDiscoverSize(float64(len(records)), msg.Topic) metrics.ObserveDiscoverSize(float64(len(records)), msg.Topic)
return resptype, protocol.DiscoverResponse{Status: protocol.OK, Records: records}, nil 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: default:
metrics.CountError("unknown") metrics.CountError("unknown")
// don't send the response // don't send the response

2
vendor/modules.txt vendored
View File

@ -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/database/sqlcipher
github.com/status-im/migrate/v4/internal/url github.com/status-im/migrate/v4/internal/url
github.com/status-im/migrate/v4/source/go_bindata 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
github.com/status-im/rendezvous/protocol github.com/status-im/rendezvous/protocol
github.com/status-im/rendezvous/server github.com/status-im/rendezvous/server