diff --git a/blockchain/v2/reactor_test.go b/blockchain/v2/reactor_test.go index d3231a4ae3..02c991cc6d 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) 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 31ce856237..0ceea3c538 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) 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 92f0106e16..8086127154 100644 --- a/p2p/mocks/peer.go +++ b/p2p/mocks/peer.go @@ -81,6 +81,20 @@ func (_m *Peer) ID() p2p.ID { return r0 } +// HasIPChanged provides a mock function with given fields: +func (_m *Peer) HasIPChanged() 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..56f2d4a063 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 } +// HasIPChanged provides a mock function for given fields: +func (_m *PeerEnvelopeSender) HasIPChanged() 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 f43cff9d51..bb04cd36c7 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 + HasIPChanged() bool // has the peer's IP changed + CloseConn() error // close original connection NodeInfo() NodeInfo // peer's info @@ -300,6 +302,18 @@ func (p *peer) IsPersistent() bool { return p.peerConn.persistent } +// 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) +} + // 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..ca92c65cba 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) 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 af0607e037..8bdaa96175 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -381,20 +381,37 @@ 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 { + sw.Logger.Error("Failed to get address for persistent peer", "peer", peer, "err", err) + return } go sw.reconnectToPeer(addr) } + + 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) + } + 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. diff --git a/test/fuzz/p2p/pex/reactor_receive.go b/test/fuzz/p2p/pex/reactor_receive.go index be9c6bba0f..7cf94c71fa 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) HasIPChanged() 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 }