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
}
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
}

View File

@ -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())
}

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/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

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/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=

View File

@ -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.

View File

@ -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
}

View File

@ -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
}

View File

@ -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

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/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