Skip to content

Commit

Permalink
Supported "network unavailable" as new state that's not alertable to …
Browse files Browse the repository at this point in the history
…eliminate false positives during Windows startup.
  • Loading branch information
menees committed Nov 15, 2020
1 parent 64ff4da commit c95cb4c
Show file tree
Hide file tree
Showing 16 changed files with 274 additions and 68 deletions.
6 changes: 3 additions & 3 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<AssemblyOriginatorKeyFile>../WirePeep.snk</AssemblyOriginatorKeyFile>

<!-- Make the assembly, file, and NuGet package versions the same. -->
<Version>1.0.3</Version>
<Version>1.0.4</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)'=='Debug'">
Expand All @@ -33,11 +33,11 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Menees.Analyzers" Version="2.0.4">
<PackageReference Include="Menees.Analyzers" Version="2.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
4 changes: 0 additions & 4 deletions src/WirePeep.Common/CommonOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public CommonOptions(ISettingsNode settingsNode)
{
this.logFileNameFormat = settingsNode.GetValue(nameof(this.LogFileNameFormat), this.LogFileNameFormat);
this.logFolder = settingsNode.GetValue(nameof(this.LogFolder), string.Empty);
this.ScrollLockSimulatesFailure = settingsNode.GetValue(nameof(this.ScrollLockSimulatesFailure), this.ScrollLockSimulatesFailure);
}

this.ValidateLogFileNameFormat();
Expand Down Expand Up @@ -67,8 +66,6 @@ public string LogFolder
}
}

public bool ScrollLockSimulatesFailure { get; set; } = ApplicationInfo.IsDebugBuild;

#endregion

#region Public Methods
Expand All @@ -77,7 +74,6 @@ public void Save(ISettingsNode settingsNode)
{
settingsNode.SetValue(nameof(this.LogFileNameFormat), this.logFileNameFormat);
settingsNode.SetValue(nameof(this.LogFolder), this.logFolder);
settingsNode.SetValue(nameof(this.ScrollLockSimulatesFailure), this.ScrollLockSimulatesFailure);
}

public string GetFullLogFileName(DateTime utcNow)
Expand Down
25 changes: 25 additions & 0 deletions src/WirePeep.Common/Enumerations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,37 @@

using System;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Text;

#endregion

namespace WirePeep
{
#region public ConnectionState

public enum ConnectionState
{
/// <summary>
/// Windows's networking stack hasn't started up yet, so we can't make network requests
/// to determine <see cref="Connected"/> or <see cref="Disconnected"/>.
/// </summary>
/// <see cref="NetworkInterface.GetIsNetworkAvailable"/>
Unavailable,

/// <summary>
/// A network connection was made to the resource.
/// </summary>
Connected,

/// <summary>
/// A network connection could not be made to the resource even though Windows's networking is available.
/// </summary>
Disconnected,
}

#endregion

#region public LogFileNameFormat

public enum LogFileNameFormat
Expand Down
33 changes: 28 additions & 5 deletions src/WirePeep.Common/LocationState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public sealed class LocationState
internal LocationState(Location location)
{
this.Location = location;
this.UpdateCounter = -2;
}

#endregion
Expand All @@ -29,7 +30,7 @@ internal LocationState(Location location)

public Location Location { get; }

public bool? IsConnected { get; private set; }
public ConnectionState Connection { get; private set; }

public TimeSpan RoundtripTime { get; private set; }

Expand All @@ -41,24 +42,46 @@ internal LocationState(Location location)

public override string ToString() => this.Location.ToString();

public bool? Update(DateTime utcNow, long counter, bool simulateFailure)
public bool? Update(DateTime utcNow, long counter, ConnectionState? simulateConnection)
{
bool? result = null;

PeerGroup peerGroup = this.Location.PeerGroup;
if (peerGroup.CanPoll(utcNow, this.lastPolled))
{
bool? wasConnected = this.IsConnected;
ConnectionState priorConnection = this.Connection;

using (Pinger pinger = new Pinger(peerGroup.Wait))
{
TimeSpan roundtripTime = TimeSpan.Zero;
this.IsConnected = !simulateFailure && pinger.TryPing(this.Location.Address, out roundtripTime);
if (simulateConnection != null)
{
this.Connection = simulateConnection.Value;
}
else
{
bool? ping = pinger.TryPing(this.Location.Address, out roundtripTime);
switch (ping)
{
case true:
this.Connection = ConnectionState.Connected;
break;

case false:
this.Connection = ConnectionState.Disconnected;
break;

default:
this.Connection = ConnectionState.Unavailable;
break;
}
}

this.RoundtripTime = roundtripTime;
this.UpdateCounter = counter;
}

result = this.IsConnected != wasConnected;
result = this.Connection != priorConnection;
this.lastPolled = utcNow;
}

Expand Down
32 changes: 17 additions & 15 deletions src/WirePeep.Common/PeerGroupState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ internal PeerGroupState(PeerGroup peerGroup)

public PeerGroup PeerGroup { get; }

public bool IsConnected { get; private set; }
public ConnectionState Connection { get; private set; }

public long UpdateCounter { get; private set; }

public DateTime? IsConnectedChanged { get; private set; }
public DateTime? ConnectionChanged { get; private set; }

public bool IsFailed { get; private set; }

Expand All @@ -46,7 +46,7 @@ internal PeerGroupState(PeerGroup peerGroup)

public override string ToString() => this.PeerGroup.ToString();

public void Update(DateTime utcNow, IReadOnlyList<LocationState> locations, bool simulateFailure)
public void Update(DateTime utcNow, IReadOnlyList<LocationState> locations, ConnectionState? simulateConnection)
{
this.LastUpdateRequest = utcNow;
if (this.PeerGroup.CanPoll(utcNow, this.LastUpdated))
Expand All @@ -57,44 +57,46 @@ public void Update(DateTime utcNow, IReadOnlyList<LocationState> locations, bool
// updated an item and so we'll round-robin through each location in the list.
this.UpdateCounter++;

bool wasConnected = this.IsConnected;
ConnectionState priorConnection = this.Connection;

bool? isPeerGroupConnected = null;
ConnectionState? peerGroupConnection = null;
int numLocations = locations.Count;
for (int i = 0; i < numLocations; i++)
{
int locationIndex = (int)unchecked((this.UpdateCounter + i) % numLocations);
LocationState locationState = locations[locationIndex];
bool? wasLocationUpdated = locationState.Update(utcNow, this.UpdateCounter, simulateFailure);
bool? wasLocationUpdated = locationState.Update(utcNow, this.UpdateCounter, simulateConnection);

// A null result means we've polled it too recently.
if (wasLocationUpdated != null)
{
// If we get a connected result, then we can quit early.
isPeerGroupConnected = locationState.IsConnected;
if (isPeerGroupConnected ?? false)
// If we get a Connected result, then we can quit early.
// If we only get Disconnected or Unavailable, then we have to check them all.
peerGroupConnection = locationState.Connection;
if (peerGroupConnection == ConnectionState.Connected)
{
break;
}
}
}

if (isPeerGroupConnected != null)
// This should only be null when we've polled all the locations too recently, so Connection shouldn't change.
if (peerGroupConnection != null)
{
this.IsConnected = isPeerGroupConnected.Value;
this.Connection = peerGroupConnection.Value;
}

if (this.IsConnected != wasConnected)
if (this.Connection != priorConnection)
{
this.IsConnectedChanged = utcNow;
this.ConnectionChanged = utcNow;
}

bool wasFailed = this.IsFailed;
if (this.IsConnected)
if (this.Connection == ConnectionState.Connected || this.Connection == ConnectionState.Unavailable)
{
this.IsFailed = false;
}
else if (!this.IsFailed && (this.IsConnectedChanged == null || utcNow >= (this.IsConnectedChanged.Value + this.PeerGroup.Fail)))
else if (!this.IsFailed && (this.ConnectionChanged == null || utcNow >= (this.ConnectionChanged.Value + this.PeerGroup.Fail)))
{
this.IsFailed = true;
}
Expand Down
12 changes: 8 additions & 4 deletions src/WirePeep.Common/Pinger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,18 @@ public static IPAddress GetAddressAtTtl(IPAddress searchAddress, int ttl, TimeSp
return result;
}

public bool CanPing(IPAddress address)
public bool? TryPing(IPAddress address)
{
PingReply reply = this.TrySend(address);
bool result = reply?.Status == IPStatus.Success;
bool? result = reply != null ? reply.Status == IPStatus.Success : null;
return result;
}

public bool TryPing(IPAddress address, out TimeSpan roundtripTime)
public bool? TryPing(IPAddress address, out TimeSpan roundtripTime)
{
PingReply reply = this.TrySend(address);
roundtripTime = TimeSpan.FromMilliseconds(reply?.RoundtripTime ?? 0);
bool result = reply?.Status == IPStatus.Success;
bool? result = reply != null ? reply.Status == IPStatus.Success : null;
return result;
}

Expand Down Expand Up @@ -99,6 +99,10 @@ private PingReply TrySend(IPAddress address)
// bit before the network stack was ready. So the internal IcmpSendEcho2 call
// returned a failure code, but it wasn't captured in the logged exception details.
// Now, I'll just treat PingExceptions the same as failed pings but without a PingReply.
//
// Technically, we could call NetworkInterface.GetIsNetworkAvailable() to check if
// Windows networking is available yet. But that polls all adapters to see if any are up.
// For efficiency, we might as well just use the exception state to indicate "Unavailable".
IDictionary<string, object> context = null;
if (ex.InnerException is Win32Exception win32)
{
Expand Down
2 changes: 1 addition & 1 deletion src/WirePeep.Common/Profile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ void AddLocation(Location location)
case "CableModem":
using (Pinger pinger = new Pinger(group.Wait))
{
address = findElement.Elements("Address").Select(e => IPAddress.Parse(e.Value)).FirstOrDefault(a => pinger.CanPing(a));
address = findElement.Elements("Address").Select(e => IPAddress.Parse(e.Value)).FirstOrDefault(a => pinger.TryPing(a) ?? false);
}

break;
Expand Down
4 changes: 2 additions & 2 deletions src/WirePeep.Common/StateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public StateManager(Profile profile)

#region Public Methods

public StateSnapshot Update(bool simulateFailure)
public StateSnapshot Update(ConnectionState? simulateConnection)
{
Dictionary<PeerGroupState, List<LocationState>> mapCopy;
lock (this.mapLock)
Expand All @@ -64,7 +64,7 @@ public StateSnapshot Update(bool simulateFailure)

DateTime utcNow = DateTime.UtcNow;
ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount / 2 };
Parallel.ForEach(mapCopy, options, pair => pair.Key.Update(utcNow, pair.Value, simulateFailure));
Parallel.ForEach(mapCopy, options, pair => pair.Key.Update(utcNow, pair.Value, simulateConnection));

StateSnapshot result = new StateSnapshot(utcNow, mapCopy.Count);
foreach (var pair in mapCopy)
Expand Down
6 changes: 4 additions & 2 deletions src/WirePeep/AppOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public void Apply(Window window)
{
using (RegistryKey runKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true))
{
// Don't let the Debug/development app blow away the Release/production app's key.
string keyName = nameof(WirePeep) + (ApplicationInfo.IsDebugBuild ? " Debug" : string.Empty);
if (this.RunAtLogin)
{
string commandLine = TextUtility.EnsureQuotes(ApplicationInfo.ExecutableFile);
Expand All @@ -86,11 +88,11 @@ public void Apply(Window window)
commandLine += " /Minimize";
}

runKey.SetValue(nameof(WirePeep), commandLine);
runKey.SetValue(keyName, commandLine);
}
else
{
runKey.DeleteValue(nameof(WirePeep), false);
runKey.DeleteValue(keyName, false);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/WirePeep/Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ internal static class Commands

public static readonly RoutedUICommand About = new RoutedUICommand(nameof(About), nameof(About), typeof(Commands));

public static readonly RoutedUICommand SimulateConnection = new RoutedUICommand("Simulate Connection", nameof(SimulateConnection), typeof(Commands));

public static readonly RoutedUICommand Unselect = new RoutedUICommand(nameof(Unselect), nameof(Unselect), typeof(Commands));

public static readonly RoutedUICommand ExportLog = new RoutedUICommand(
Expand Down
2 changes: 1 addition & 1 deletion src/WirePeep/LocationDialog.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ private void OKClicked(object sender, RoutedEventArgs e)
const int WaitMilliseconds = 200;
using (Pinger pinger = new Pinger(TimeSpan.FromMilliseconds(WaitMilliseconds)))
{
if (!pinger.CanPing(location.Address))
if (!(pinger.TryPing(location.Address) ?? false))
{
// Occasionally, a ping will be lost, and it's ok. The user might also want to configure
// a site to watch that is known to be intermittently available.
Expand Down
Loading

0 comments on commit c95cb4c

Please sign in to comment.