From dcc7464f46d14ecc9e09a16b5e64029d618a08b8 Mon Sep 17 00:00:00 2001 From: Kleissner Date: Mon, 1 Mar 2021 01:05:37 +0100 Subject: [PATCH] New function IsNetworkErrorFatal to invalidate connections on fatal network errors. Use connection.Expires field to trigger removal of inactive connections after a while. --- Commands.go | 11 +++++++---- Connection.go | 10 ++++++++-- Network Detection.go | 20 +++++++++++++++++++- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/Commands.go b/Commands.go index 1589696..69d2977 100644 --- a/Commands.go +++ b/Commands.go @@ -98,7 +98,6 @@ func autoPingAll() { for { time.Sleep(time.Second) thresholdInvalidate := time.Now().Add(-connectionInvalidate * time.Second) - thresholdRemove := time.Now().Add(-(connectionRemove + connectionInvalidate) * time.Second) thresholdPingOut := time.Now().Add(-pingTime * time.Second) for _, peer := range PeerlistGet() { @@ -107,19 +106,23 @@ func autoPingAll() { // Check if no incoming packet for the last X seconds. Regularly sent pings should result in incoming packets. if connection.LastPacketIn.Before(thresholdInvalidate) { peer.invalidateActiveConnection(connection) + continue } // Send ping if none was sent recently and no incoming packet was received recently. if connection.LastPacketIn.Before(thresholdPingOut) && connection.LastPingOut.Before(thresholdPingOut) { - peer.sendConnection(&PacketRaw{Command: CommandPing}, connection) + if err := peer.sendConnection(&PacketRaw{Command: CommandPing}, connection); IsNetworkErrorFatal(err) { + peer.invalidateActiveConnection(connection) + } connection.LastPingOut = time.Now() + continue } } // handle inactive connections for _, connection := range peer.GetConnections(false) { - // Remove connections that have been inactive for a long time; only if there is at least an active connection, or at least two other inactive ones. - if connection.LastPacketIn.Before(thresholdRemove) && (len(peer.connectionActive) >= 1 || len(peer.connectionInactive) > 2) { + // If the inactive connection is expired, remove it; although only if there is at least one active connection, or two other inactive ones. + if (len(peer.connectionActive) >= 1 || len(peer.connectionInactive) > 2) && connection.Expires.Before(time.Now()) { peer.removeInactiveConnection(connection) continue } diff --git a/Connection.go b/Connection.go index b25541f..c626e44 100644 --- a/Connection.go +++ b/Connection.go @@ -23,6 +23,7 @@ type Connection struct { LastPacketIn time.Time // Last time an incoming packet was received. LastPacketOut time.Time // Last time an outgoing packet was attempted to send. LastPingOut time.Time // Last ping out. + Expires time.Time // Inactive connections only: Expiry date. If it does not become active by that date, it will be considered expired and removed. } // Equal checks if the connection was established other the same network adapter using the same IP address. Port is intentionally not checked. @@ -94,6 +95,9 @@ func (peer *PeerInfo) invalidateActiveConnection(input *Connection) { peer.Lock() defer peer.Unlock() + // Change the status to inactive and start the expiration. If the connection does not become valid by that date, it will be removed. + input.Expires = time.Now().Add(connectionRemove * time.Second) + // remove from connectionLatest if selected so it won't be used by standard send function if peer.connectionLatest == input { peer.connectionLatest = nil @@ -111,7 +115,7 @@ func (peer *PeerInfo) invalidateActiveConnection(input *Connection) { } peer.connectionActive = activeNew - return + break } } } @@ -166,7 +170,9 @@ func (peer *PeerInfo) send(packet *PacketRaw) (err error) { // Invalid connection, immediately invalidate. Fallback to broadcast to all other active ones. // Windows: A common error when the network adapter is disabled is "wsasendto: The requested address is not valid in its context". - peer.invalidateActiveConnection(c) + if IsNetworkErrorFatal(err) { + peer.invalidateActiveConnection(c) + } } // If no latest connection available, broadcast on all available connections. diff --git a/Network Detection.go b/Network Detection.go index 0ad6ec9..6f308c1 100644 --- a/Network Detection.go +++ b/Network Detection.go @@ -6,7 +6,10 @@ Author: Peter Kleissner package core -import "net" +import ( + "net" + "strings" +) // FindInterfaceByIP finds an interface based on the IP. The IP must be available at the interface. func FindInterfaceByIP(ip net.IP) (iface *net.Interface, ipnet *net.IPNet) { @@ -69,3 +72,18 @@ func IsIPv4(IP net.IP) bool { func IsIPv6(IP net.IP) bool { return IP.To4() == nil && IP.To16() != nil } + +// IsNetworkErrorFatal checks if a network error indicates a broken connection. +// Not every network error indicates a broken connection. This function prevents from over-dropping connections. +func IsNetworkErrorFatal(err error) bool { + if err == nil { + return false + } + + // Windows: A common error when the network adapter is disabled is "wsasendto: The requested address is not valid in its context". + if strings.Contains(err.Error(), "requested address is not valid in its context") { + return true + } + + return false +}