From 5f27f7b444c6e328c918fe2614aa5b5ff0ceb669 Mon Sep 17 00:00:00 2001 From: Nina Barbakadze <barbakadzeninaa@gmail.com> Date: Wed, 31 Jul 2024 15:35:34 +0200 Subject: [PATCH 1/5] fix: DNS TTL not respected --- p2p/peer.go | 13 +++++++++---- p2p/peer_set_test.go | 1 + p2p/switch.go | 38 +++++++++++++++++++++++++++----------- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index f43cff9d51..b77be635d8 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -32,6 +32,8 @@ type Peer interface { IsOutbound() bool // did we dial the peer IsPersistent() bool // do we redial this peer when we disconnect + IPHasChanged() bool // has the peer's IP changed + CloseConn() error // close original connection NodeInfo() NodeInfo // peer's info @@ -138,10 +140,6 @@ func (pc peerConn) ID() ID { // Return the IP from the connection RemoteAddr func (pc peerConn) RemoteIP() net.IP { - if pc.ip != nil { - return pc.ip - } - host, _, err := net.SplitHostPort(pc.conn.RemoteAddr().String()) if err != nil { panic(err) @@ -300,6 +298,13 @@ func (p *peer) IsPersistent() bool { return p.peerConn.persistent } +// IPHasChanged returns true and the new IP if the peer's IP has changed. +func (p *peer) IPHasChanged() bool { + oldIP := p.ip + newIP := p.RemoteIP() + return !oldIP.Equal(newIP) +} + // NodeInfo returns a copy of the peer's NodeInfo. func (p *peer) NodeInfo() NodeInfo { return p.nodeInfo diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index 6501dd77a5..d1f9b5b139 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -27,6 +27,7 @@ func (mp *mockPeer) NodeInfo() NodeInfo { return DefaultNodeInfo{} func (mp *mockPeer) Status() ConnectionStatus { return ConnectionStatus{} } func (mp *mockPeer) ID() ID { return mp.id } func (mp *mockPeer) IsOutbound() bool { return false } +func (mp *mockPeer) IPHasChanged() bool { return true } func (mp *mockPeer) IsPersistent() bool { return true } func (mp *mockPeer) Get(s string) interface{} { return s } func (mp *mockPeer) Set(string, interface{}) {} diff --git a/p2p/switch.go b/p2p/switch.go index af0607e037..140056f52c 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -381,22 +381,38 @@ func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { sw.stopAndRemovePeer(peer, reason) if peer.IsPersistent() { - var addr *NetAddress - if peer.IsOutbound() { // socket address for outbound peers - addr = peer.SocketAddr() - } else { // self-reported address for inbound peers - var err error - addr, err = peer.NodeInfo().NetAddress() - if err != nil { - sw.Logger.Error("Wanted to reconnect to inbound peer, but self-reported address is wrong", - "peer", peer, "err", err) - return - } + addr, err := sw.getPeerAddress(peer) + if err != nil { + return + } + go sw.reconnectToPeer(addr) + } + + if peer.IPHasChanged() { + addr, err := sw.getPeerAddress(peer) + if err != nil { + return } go sw.reconnectToPeer(addr) } } +// getPeerAddress returns the appropriate NetAddress for a given peer, +// handling both outbound and inbound peers. +func (sw *Switch) getPeerAddress(peer Peer) (*NetAddress, error) { + if peer.IsOutbound() { + return peer.SocketAddr(), nil + } + // For inbound peers, get the self-reported address + addr, err := peer.NodeInfo().NetAddress() + if err != nil { + sw.Logger.Error("Failed to get address for inbound peer", + "peer", peer, "err", err) + return nil, err + } + return addr, nil +} + // StopPeerGracefully disconnects from a peer gracefully. // TODO: handle graceful disconnects. func (sw *Switch) StopPeerGracefully(peer Peer) { From e3dc4cfac30ad1fa9ebed937262fce1dc63a1323 Mon Sep 17 00:00:00 2001 From: Nina Barbakadze <barbakadzeninaa@gmail.com> Date: Wed, 31 Jul 2024 16:18:27 +0200 Subject: [PATCH 2/5] fix: compile errors --- blockchain/v2/reactor_test.go | 1 + p2p/mock/peer.go | 1 + p2p/mocks/peer.go | 14 ++++++++++++++ p2p/mocks/peer_envelope_sender.go | 14 ++++++++++++++ p2p/peer.go | 2 +- p2p/peer_set_test.go | 2 +- test/fuzz/p2p/pex/reactor_receive.go | 6 ++++-- 7 files changed, 36 insertions(+), 4 deletions(-) diff --git a/blockchain/v2/reactor_test.go b/blockchain/v2/reactor_test.go index d3231a4ae3..bb3afe4953 100644 --- a/blockchain/v2/reactor_test.go +++ b/blockchain/v2/reactor_test.go @@ -43,6 +43,7 @@ func (mp mockPeer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.RemoteIP() func (mp mockPeer) IsOutbound() bool { return true } func (mp mockPeer) IsPersistent() bool { return true } +func (mp mockPeer) IPHasChanged() bool { return false } func (mp mockPeer) CloseConn() error { return nil } func (mp mockPeer) NodeInfo() p2p.NodeInfo { diff --git a/p2p/mock/peer.go b/p2p/mock/peer.go index 31ce856237..ca56085c04 100644 --- a/p2p/mock/peer.go +++ b/p2p/mock/peer.go @@ -57,6 +57,7 @@ func (mp *Peer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} func (mp *Peer) ID() p2p.ID { return mp.id } func (mp *Peer) IsOutbound() bool { return mp.Outbound } func (mp *Peer) IsPersistent() bool { return mp.Persistent } +func (mp *Peer) IPHasChanged() bool { return false } func (mp *Peer) Get(key string) interface{} { if value, ok := mp.kv[key]; ok { return value diff --git a/p2p/mocks/peer.go b/p2p/mocks/peer.go index 92f0106e16..aa37f65d3d 100644 --- a/p2p/mocks/peer.go +++ b/p2p/mocks/peer.go @@ -81,6 +81,20 @@ func (_m *Peer) ID() p2p.ID { return r0 } +// IPHasChanged provides a mock function with given fields: +func (_m *Peer) IPHasChanged() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // IsOutbound provides a mock function with given fields: func (_m *Peer) IsOutbound() bool { ret := _m.Called() diff --git a/p2p/mocks/peer_envelope_sender.go b/p2p/mocks/peer_envelope_sender.go index 89f231104d..b896c3adac 100644 --- a/p2p/mocks/peer_envelope_sender.go +++ b/p2p/mocks/peer_envelope_sender.go @@ -109,6 +109,20 @@ func (_m *PeerEnvelopeSender) IsPersistent() bool { return r0 } +// IPHasChanged provides a mock function for given fields: +func (_m *PeerEnvelopeSender) IPHasChanged() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // IsRunning provides a mock function with given fields: func (_m *PeerEnvelopeSender) IsRunning() bool { ret := _m.Called() diff --git a/p2p/peer.go b/p2p/peer.go index b77be635d8..8912c81cb9 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -302,7 +302,7 @@ func (p *peer) IsPersistent() bool { func (p *peer) IPHasChanged() bool { oldIP := p.ip newIP := p.RemoteIP() - return !oldIP.Equal(newIP) + return !oldIP.Equal(newIP) } // NodeInfo returns a copy of the peer's NodeInfo. diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index d1f9b5b139..5ab936a57c 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -27,7 +27,7 @@ func (mp *mockPeer) NodeInfo() NodeInfo { return DefaultNodeInfo{} func (mp *mockPeer) Status() ConnectionStatus { return ConnectionStatus{} } func (mp *mockPeer) ID() ID { return mp.id } func (mp *mockPeer) IsOutbound() bool { return false } -func (mp *mockPeer) IPHasChanged() bool { return true } +func (mp *mockPeer) IPHasChanged() bool { return false } func (mp *mockPeer) IsPersistent() bool { return true } func (mp *mockPeer) Get(s string) interface{} { return s } func (mp *mockPeer) Set(string, interface{}) {} diff --git a/test/fuzz/p2p/pex/reactor_receive.go b/test/fuzz/p2p/pex/reactor_receive.go index be9c6bba0f..861e51bd28 100644 --- a/test/fuzz/p2p/pex/reactor_receive.go +++ b/test/fuzz/p2p/pex/reactor_receive.go @@ -74,8 +74,10 @@ func (fp *fuzzPeer) RemoteIP() net.IP { return net.IPv4(0, 0, 0, 0) } func (fp *fuzzPeer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: fp.RemoteIP(), Port: 98991, Zone: ""} } -func (fp *fuzzPeer) IsOutbound() bool { return false } -func (fp *fuzzPeer) IsPersistent() bool { return false } +func (fp *fuzzPeer) IsOutbound() bool { return false } +func (fp *fuzzPeer) IsPersistent() bool { return false } +func (fp *fuzzPeer) IPHasChanged() bool { return false } + func (fp *fuzzPeer) CloseConn() error { return nil } func (fp *fuzzPeer) NodeInfo() p2p.NodeInfo { return defaultNodeInfo } func (fp *fuzzPeer) Status() p2p.ConnectionStatus { var cs p2p.ConnectionStatus; return cs } From c80f553347aaf0be1469bd9b3ac15bdd2c60bc6d Mon Sep 17 00:00:00 2001 From: Nina Barbakadze <barbakadzeninaa@gmail.com> Date: Wed, 31 Jul 2024 16:33:41 +0200 Subject: [PATCH 3/5] fix: do not refetch the ip if current ip is zero --- p2p/peer.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/p2p/peer.go b/p2p/peer.go index 8912c81cb9..1076fe7f45 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -301,6 +301,9 @@ func (p *peer) IsPersistent() bool { // IPHasChanged returns true and the new IP if the peer's IP has changed. func (p *peer) IPHasChanged() bool { oldIP := p.ip + if oldIP == nil { + return false + } newIP := p.RemoteIP() return !oldIP.Equal(newIP) } From 02032accf9161daf63d04b76df998f8ae7dc0fa3 Mon Sep 17 00:00:00 2001 From: Nina Barbakadze <barbakadzeninaa@gmail.com> Date: Wed, 31 Jul 2024 16:50:11 +0200 Subject: [PATCH 4/5] refactor: make errors more specific --- p2p/switch.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/p2p/switch.go b/p2p/switch.go index 140056f52c..0795049ff9 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -383,6 +383,7 @@ func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { if peer.IsPersistent() { addr, err := sw.getPeerAddress(peer) if err != nil { + sw.Logger.Error("Failed to get address for persistent peer", "peer", peer, "err", err) return } go sw.reconnectToPeer(addr) @@ -391,7 +392,7 @@ func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { if peer.IPHasChanged() { addr, err := sw.getPeerAddress(peer) if err != nil { - return + sw.Logger.Error("Failed to get address for peer with changed IP", "peer", peer, "err", err) } go sw.reconnectToPeer(addr) } From 9c47113e275bef1af2cf36263061ed35288b890e Mon Sep 17 00:00:00 2001 From: Nina Barbakadze <barbakadzeninaa@gmail.com> Date: Wed, 31 Jul 2024 17:26:44 +0200 Subject: [PATCH 5/5] refactor: address review comments --- blockchain/v2/reactor_test.go | 2 +- p2p/mock/peer.go | 2 +- p2p/mocks/peer.go | 4 ++-- p2p/mocks/peer_envelope_sender.go | 4 ++-- p2p/peer.go | 12 +++++++++--- p2p/peer_set_test.go | 2 +- p2p/switch.go | 2 +- test/fuzz/p2p/pex/reactor_receive.go | 2 +- 8 files changed, 18 insertions(+), 12 deletions(-) diff --git a/blockchain/v2/reactor_test.go b/blockchain/v2/reactor_test.go index bb3afe4953..02c991cc6d 100644 --- a/blockchain/v2/reactor_test.go +++ b/blockchain/v2/reactor_test.go @@ -43,7 +43,7 @@ func (mp mockPeer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.RemoteIP() func (mp mockPeer) IsOutbound() bool { return true } func (mp mockPeer) IsPersistent() bool { return true } -func (mp mockPeer) IPHasChanged() bool { return false } +func (mp mockPeer) HasIPChanged() bool { return false } func (mp mockPeer) CloseConn() error { return nil } func (mp mockPeer) NodeInfo() p2p.NodeInfo { diff --git a/p2p/mock/peer.go b/p2p/mock/peer.go index ca56085c04..0ceea3c538 100644 --- a/p2p/mock/peer.go +++ b/p2p/mock/peer.go @@ -57,7 +57,7 @@ func (mp *Peer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} func (mp *Peer) ID() p2p.ID { return mp.id } func (mp *Peer) IsOutbound() bool { return mp.Outbound } func (mp *Peer) IsPersistent() bool { return mp.Persistent } -func (mp *Peer) IPHasChanged() bool { return false } +func (mp *Peer) HasIPChanged() bool { return false } func (mp *Peer) Get(key string) interface{} { if value, ok := mp.kv[key]; ok { return value diff --git a/p2p/mocks/peer.go b/p2p/mocks/peer.go index aa37f65d3d..8086127154 100644 --- a/p2p/mocks/peer.go +++ b/p2p/mocks/peer.go @@ -81,8 +81,8 @@ func (_m *Peer) ID() p2p.ID { return r0 } -// IPHasChanged provides a mock function with given fields: -func (_m *Peer) IPHasChanged() bool { +// HasIPChanged provides a mock function with given fields: +func (_m *Peer) HasIPChanged() bool { ret := _m.Called() var r0 bool diff --git a/p2p/mocks/peer_envelope_sender.go b/p2p/mocks/peer_envelope_sender.go index b896c3adac..56f2d4a063 100644 --- a/p2p/mocks/peer_envelope_sender.go +++ b/p2p/mocks/peer_envelope_sender.go @@ -109,8 +109,8 @@ func (_m *PeerEnvelopeSender) IsPersistent() bool { return r0 } -// IPHasChanged provides a mock function for given fields: -func (_m *PeerEnvelopeSender) IPHasChanged() bool { +// HasIPChanged provides a mock function for given fields: +func (_m *PeerEnvelopeSender) HasIPChanged() bool { ret := _m.Called() var r0 bool diff --git a/p2p/peer.go b/p2p/peer.go index 1076fe7f45..bb04cd36c7 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -32,7 +32,7 @@ type Peer interface { IsOutbound() bool // did we dial the peer IsPersistent() bool // do we redial this peer when we disconnect - IPHasChanged() bool // has the peer's IP changed + HasIPChanged() bool // has the peer's IP changed CloseConn() error // close original connection @@ -140,6 +140,10 @@ func (pc peerConn) ID() ID { // Return the IP from the connection RemoteAddr func (pc peerConn) RemoteIP() net.IP { + if pc.ip != nil { + return pc.ip + } + host, _, err := net.SplitHostPort(pc.conn.RemoteAddr().String()) if err != nil { panic(err) @@ -298,12 +302,14 @@ func (p *peer) IsPersistent() bool { return p.peerConn.persistent } -// IPHasChanged returns true and the new IP if the peer's IP has changed. -func (p *peer) IPHasChanged() bool { +// HasIPChanged returns true and the new IP if the peer's IP has changed. +func (p *peer) HasIPChanged() bool { oldIP := p.ip if oldIP == nil { return false } + // Reset the IP so we can get the new one + p.ip = nil newIP := p.RemoteIP() return !oldIP.Equal(newIP) } diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index 5ab936a57c..ca92c65cba 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -27,7 +27,7 @@ func (mp *mockPeer) NodeInfo() NodeInfo { return DefaultNodeInfo{} func (mp *mockPeer) Status() ConnectionStatus { return ConnectionStatus{} } func (mp *mockPeer) ID() ID { return mp.id } func (mp *mockPeer) IsOutbound() bool { return false } -func (mp *mockPeer) IPHasChanged() bool { return false } +func (mp *mockPeer) HasIPChanged() bool { return false } func (mp *mockPeer) IsPersistent() bool { return true } func (mp *mockPeer) Get(s string) interface{} { return s } func (mp *mockPeer) Set(string, interface{}) {} diff --git a/p2p/switch.go b/p2p/switch.go index 0795049ff9..8bdaa96175 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -389,7 +389,7 @@ func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { go sw.reconnectToPeer(addr) } - if peer.IPHasChanged() { + if peer.HasIPChanged() { addr, err := sw.getPeerAddress(peer) if err != nil { sw.Logger.Error("Failed to get address for peer with changed IP", "peer", peer, "err", err) diff --git a/test/fuzz/p2p/pex/reactor_receive.go b/test/fuzz/p2p/pex/reactor_receive.go index 861e51bd28..7cf94c71fa 100644 --- a/test/fuzz/p2p/pex/reactor_receive.go +++ b/test/fuzz/p2p/pex/reactor_receive.go @@ -76,7 +76,7 @@ func (fp *fuzzPeer) RemoteAddr() net.Addr { } func (fp *fuzzPeer) IsOutbound() bool { return false } func (fp *fuzzPeer) IsPersistent() bool { return false } -func (fp *fuzzPeer) IPHasChanged() bool { return false } +func (fp *fuzzPeer) HasIPChanged() bool { return false } func (fp *fuzzPeer) CloseConn() error { return nil } func (fp *fuzzPeer) NodeInfo() p2p.NodeInfo { return defaultNodeInfo }