├── .editorconfig ├── .gitattributes ├── .gitignore ├── LICENSE ├── PowerPing.sln ├── README.md ├── run-tests.ps1 ├── scripts ├── build_core_mono.sh ├── build_dotnet_core.bat ├── build_dotnet_framework.bat └── vswhere.exe ├── src ├── Commands │ ├── Flood.cs │ ├── Graph.cs │ ├── Listen.cs │ ├── Lookup.cs │ ├── Scan.cs │ └── Trace.cs ├── Display │ ├── ConsoleDisplay.cs │ ├── ConsoleMessageHandler.cs │ ├── DisplayConfiguration.cs │ └── Strings.cs ├── File │ ├── LogFile.cs │ └── LogMessageHandler.cs ├── Network │ ├── ICMP.cs │ ├── IPv4.cs │ └── Ping.cs ├── PowerPing.csproj ├── Program.cs ├── Properties │ ├── App.config │ ├── AssemblyInfo.cs │ └── app.manifest ├── Structures │ ├── PingAttributes.cs │ ├── PingError.cs │ ├── PingReply.cs │ ├── PingRequest.cs │ ├── PingResults.cs │ └── PingTimeout.cs └── Utils │ ├── CommandLine.cs │ ├── Exceptions.cs │ ├── ExtensionMethods.cs │ ├── Helper.cs │ └── RateLimiter.cs └── tests ├── test-argument-parsing.ps1 ├── test-core-functions.ps1 ├── test-lookup-functions.ps1 └── visual-tests.ps1 /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Enable automatic end-of-line normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Debug 2 | Release 3 | 4 | 5 | build/ 6 | 7 | .vscode/* 8 | 9 | .DS_Store 10 | 11 | # Created by https://www.gitignore.io/api/visualstudio 12 | 13 | ### VisualStudio ### 14 | ## Ignore Visual Studio temporary files, build results, and 15 | ## files generated by popular Visual Studio add-ons. 16 | ## 17 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 18 | 19 | # User-specific files 20 | *.suo 21 | *.user 22 | *.userosscache 23 | *.sln.docstates 24 | 25 | # User-specific files (MonoDevelop/Xamarin Studio) 26 | *.userprefs 27 | 28 | # Build results 29 | [Dd]ebug/ 30 | [Dd]ebugPublic/ 31 | [Rr]elease/ 32 | [Rr]eleases/ 33 | x64/ 34 | x86/ 35 | bld/ 36 | [Bb]in/ 37 | [Oo]bj/ 38 | [Ll]og/ 39 | 40 | # Visual Studio 2015 cache/options directory 41 | .vs/ 42 | # Uncomment if you have tasks that create the project's static files in wwwroot 43 | #wwwroot/ 44 | 45 | # MSTest test Results 46 | [Tt]est[Rr]esult*/ 47 | [Bb]uild[Ll]og.* 48 | 49 | # NUNIT 50 | *.VisualState.xml 51 | TestResult.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # .NET Core 59 | project.lock.json 60 | project.fragment.lock.json 61 | artifacts/ 62 | **/Properties/launchSettings.json 63 | 64 | *_i.c 65 | *_p.c 66 | *_i.h 67 | *.ilk 68 | *.meta 69 | *.obj 70 | *.pch 71 | *.pdb 72 | *.pgc 73 | *.pgd 74 | *.rsp 75 | *.sbr 76 | *.tlb 77 | *.tli 78 | *.tlh 79 | *.tmp 80 | *.tmp_proj 81 | *.log 82 | *.vspscc 83 | *.vssscc 84 | .builds 85 | *.pidb 86 | *.svclog 87 | *.scc 88 | 89 | # Chutzpah Test files 90 | _Chutzpah* 91 | 92 | # Visual C++ cache files 93 | ipch/ 94 | *.aps 95 | *.ncb 96 | *.opendb 97 | *.opensdf 98 | *.sdf 99 | *.cachefile 100 | *.VC.db 101 | *.VC.VC.opendb 102 | 103 | # Visual Studio profiler 104 | *.psess 105 | *.vsp 106 | *.vspx 107 | *.sap 108 | 109 | # TFS 2012 Local Workspace 110 | $tf/ 111 | 112 | # Guidance Automation Toolkit 113 | *.gpState 114 | 115 | # ReSharper is a .NET coding add-in 116 | _ReSharper*/ 117 | *.[Rr]e[Ss]harper 118 | *.DotSettings.user 119 | 120 | # JustCode is a .NET coding add-in 121 | .JustCode 122 | 123 | # TeamCity is a build add-in 124 | _TeamCity* 125 | 126 | # DotCover is a Code Coverage Tool 127 | *.dotCover 128 | 129 | # Visual Studio code coverage results 130 | *.coverage 131 | *.coveragexml 132 | 133 | # NCrunch 134 | _NCrunch_* 135 | .*crunch*.local.xml 136 | nCrunchTemp_* 137 | 138 | # MightyMoose 139 | *.mm.* 140 | AutoTest.Net/ 141 | 142 | # Web workbench (sass) 143 | .sass-cache/ 144 | 145 | # Installshield output folder 146 | [Ee]xpress/ 147 | 148 | # DocProject is a documentation generator add-in 149 | DocProject/buildhelp/ 150 | DocProject/Help/*.HxT 151 | DocProject/Help/*.HxC 152 | DocProject/Help/*.hhc 153 | DocProject/Help/*.hhk 154 | DocProject/Help/*.hhp 155 | DocProject/Help/Html2 156 | DocProject/Help/html 157 | 158 | # Click-Once directory 159 | publish/ 160 | 161 | # Publish Web Output 162 | *.[Pp]ublish.xml 163 | *.azurePubxml 164 | # TODO: Comment the next line if you want to checkin your web deploy settings 165 | # but database connection strings (with potential passwords) will be unencrypted 166 | *.pubxml 167 | *.publishproj 168 | 169 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 170 | # checkin your Azure Web App publish settings, but sensitive information contained 171 | # in these scripts will be unencrypted 172 | PublishScripts/ 173 | 174 | # NuGet Packages 175 | *.nupkg 176 | # The packages folder can be ignored because of Package Restore 177 | **/packages/* 178 | # except build/, which is used as an MSBuild target. 179 | !**/packages/build/ 180 | # Uncomment if necessary however generally it will be regenerated when needed 181 | #!**/packages/repositories.config 182 | # NuGet v3's project.json files produces more ignorable files 183 | *.nuget.props 184 | *.nuget.targets 185 | 186 | # Microsoft Azure Build Output 187 | csx/ 188 | *.build.csdef 189 | 190 | # Microsoft Azure Emulator 191 | ecf/ 192 | rcf/ 193 | 194 | # Windows Store app package directories and files 195 | AppPackages/ 196 | BundleArtifacts/ 197 | Package.StoreAssociation.xml 198 | _pkginfo.txt 199 | 200 | # Visual Studio cache files 201 | # files ending in .cache can be ignored 202 | *.[Cc]ache 203 | # but keep track of directories ending in .cache 204 | !*.[Cc]ache/ 205 | 206 | # Others 207 | ClientBin/ 208 | ~$* 209 | *~ 210 | *.dbmdl 211 | *.dbproj.schemaview 212 | *.jfm 213 | *.pfx 214 | *.publishsettings 215 | orleans.codegen.cs 216 | 217 | # Since there are multiple workflows, uncomment next line to ignore bower_components 218 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 219 | #bower_components/ 220 | 221 | # RIA/Silverlight projects 222 | Generated_Code/ 223 | 224 | # Backup & report files from converting an old project file 225 | # to a newer Visual Studio version. Backup files are not needed, 226 | # because we have git ;-) 227 | _UpgradeReport_Files/ 228 | Backup*/ 229 | UpgradeLog*.XML 230 | UpgradeLog*.htm 231 | 232 | # SQL Server files 233 | *.mdf 234 | *.ldf 235 | *.ndf 236 | 237 | # Business Intelligence projects 238 | *.rdl.data 239 | *.bim.layout 240 | *.bim_*.settings 241 | 242 | # Microsoft Fakes 243 | FakesAssemblies/ 244 | 245 | # GhostDoc plugin setting file 246 | *.GhostDoc.xml 247 | 248 | # Node.js Tools for Visual Studio 249 | .ntvs_analysis.dat 250 | node_modules/ 251 | 252 | # Typescript v1 declaration files 253 | typings/ 254 | 255 | # Visual Studio 6 build log 256 | *.plg 257 | 258 | # Visual Studio 6 workspace options file 259 | *.opt 260 | 261 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 262 | *.vbw 263 | 264 | # Visual Studio LightSwitch build output 265 | **/*.HTMLClient/GeneratedArtifacts 266 | **/*.DesktopClient/GeneratedArtifacts 267 | **/*.DesktopClient/ModelManifest.xml 268 | **/*.Server/GeneratedArtifacts 269 | **/*.Server/ModelManifest.xml 270 | _Pvt_Extensions 271 | 272 | # Paket dependency manager 273 | .paket/paket.exe 274 | paket-files/ 275 | 276 | # FAKE - F# Make 277 | .fake/ 278 | 279 | # JetBrains Rider 280 | .idea/ 281 | *.sln.iml 282 | 283 | # CodeRush 284 | .cr/ 285 | 286 | # Python Tools for Visual Studio (PTVS) 287 | __pycache__/ 288 | *.pyc 289 | 290 | # Cake - Uncomment if you are using it 291 | # tools/** 292 | # !tools/packages.config 293 | 294 | # Telerik's JustMock configuration file 295 | *.jmconfig 296 | 297 | # BizTalk build output 298 | *.btp.cs 299 | *.btm.cs 300 | *.odx.cs 301 | *.xsd.cs 302 | 303 | # End of https://www.gitignore.io/api/visualstudio -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Matthew Carney 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PowerPing.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32126.317 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerPing", "src\PowerPing.csproj", "{12C0BD4F-2D94-4B4E-B55E-815F90252573}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {12C0BD4F-2D94-4B4E-B55E-815F90252573}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {12C0BD4F-2D94-4B4E-B55E-815F90252573}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {12C0BD4F-2D94-4B4E-B55E-815F90252573}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {12C0BD4F-2D94-4B4E-B55E-815F90252573}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {B845EC06-F0A0-450C-A016-54AF90DA63C0} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerPing - Advanced Windows Ping 2 | 3 | [![](https://img.shields.io/badge/stable%20version-1.3.5-brightgreen.svg)](https://github.com/Killeroo/PowerPing/releases) 4 | 5 | Small improved command line ICMP ping program. 6 | 7 | ![alt text](https://user-images.githubusercontent.com/9999745/74611062-8ad7e800-50f0-11ea-880c-17b7a76a0bab.png "PowerPing in action") 8 | 9 | # Downloads 10 | Stable releases can be downloaded [[here]](https://github.com/Killeroo/PowerPing/releases) 11 | 12 | ## Features 13 | 14 | - Basic ping functionality 15 | - Coloured output 16 | - Display options 17 | - [ICMP packet customisation](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol#Control_messages) 18 | - [Scanning](https://en.wikipedia.org/wiki/Ping_sweep) 19 | - [Flooding](https://en.wikipedia.org/wiki/Ping_flood) 20 | - [ICMP packet capture (/listen)](docs/screenshots/screenshot3.png) 21 | - [IP location lookup](docs/screenshots/screenshot4.png) 22 | - [Whois lookup](https://en.wikipedia.org/wiki/WHOIS) 23 | - [Graphing](docs/screenshots/screenshot2.png) 24 | 25 | _Future features:_ 26 | 27 | - [Traceroute](https://en.wikipedia.org/wiki/Traceroute) 28 | - [Tunnelling](https://en.wikipedia.org/wiki/ICMP_tunnel) 29 | - [IPv6/ICMPv6](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol_version_6) 30 | 31 | ## Arguments: 32 | Ping Options: 33 | --infinite [--t] Ping the target until stopped (Ctrl-C to stop) 34 | --ipv4 [--4] Force using IPv4 35 | --random [--rng] Generates random ICMP message 36 | --dontfrag [--df] Set 'Don't Fragment' flag 37 | --buffer [--rb] number Sets recieve buffer size (default is 5096) 38 | --beep [--b] number Beep on timeout (1) or on reply (2) 39 | --count [--c] number Number of pings to send 40 | --timeout [--w] number Time to wait for reply (in milliseconds) 41 | --ttl [--i] number Time To Live for packet 42 | --interval [--in] number Interval between each ping (in milliseconds) 43 | --type [--pt] number Use custom ICMP type 44 | --code [--pc] number Use custom ICMP code value 45 | --size [--s] number Set size (in bytes) of packet (overwrites packet message) 46 | --message [--m] message Ping packet message 47 | --timing [--ti] timing Timing levels: 48 | 0 - Paranoid 4 - Nimble 49 | 1 - Sneaky 5 - Speedy 50 | 2 - Quiet 6 - Insane 51 | 3 - Polite 7 - Random 52 | 53 | Display Options: 54 | --shorthand [--sh] Show less detailed replies 55 | --displaymsg [--dm] Display ICMP message contents 56 | --timestamp [--ts] Display timestamps (add 'UTC' for Coordinated Universal Time) 57 | --fulltimestamp [--fts] Display full timestamps with localised date and time 58 | --nocolor [--nc] No colour 59 | --symbols [--sym] Renders replies and timeouts as ASCII symbols (add '1' for alt theme) 60 | --requests [--r] Show request packets 61 | --notimeouts [--nt] Don't display timeout messages 62 | --quiet [--q] No output (only affects normal ping) 63 | --resolve [--res] Resolve hostname of response address from DNS 64 | --inputaddr [--ia] Show input address instead of revolved one 65 | --checksum [--chk] Display checksum of packet 66 | --requireinput [--ri] Always ask for user input upon completion 67 | --limit [--l] number Limits output to just replies (1), requests (2) or summary(3) 68 | --decimals [--dp] number Num of decimal places to use (0 to 3) 69 | --low number Defines the low response time threshold (which times are coloured green) 70 | --mid number Defines the mid response time threshold (which times are coloured yellow) 71 | --high number Defines the high response time threshold (which times are coloured red) 72 | 73 | Modes: 74 | --scan [--sc] address Network scanning, specify range "127.0.0.1-55" 75 | (listen command without address will listen on all local network adapters) 76 | --flood [--fl] address Send high volume of pings to address 77 | --graph [--g] address Graph view 78 | --location [--loc] address Location info for an address 79 | --listen [--li] address Listen for ICMP packets on an address 80 | --listen [--li] Listen for ICMP packets on all local network adapters 81 | --whois address Whois lookup for an address 82 | --whoami Location info for current host 83 | 84 | Others: 85 | --log [--f] path Logs the ping output to a file 86 | --help [--?] Displays this help message 87 | --version [--v] Shows version and build information 88 | 89 | ## Example usage: 90 | powerping 8.8.8.8 - Send ping to google DNS with default values (3000ms timeout, 5 pings) 91 | powerping github.com --w 500 --t - Send pings indefinitely to github.com with a 500ms timeout 92 | powerping --w 500 --t github.com - Address can also be specified at the end 93 | 94 | powerping 127.0.0.1 --m Meow - Send ping with packet message "Meow" to loopback address 95 | powerping 127.0.0.1 --pt 3 --pc 2 - Send ping with ICMP type 3 (dest unreachable) and code 2 96 | 97 | powerping 8.8.8.8 /c 5 -w 500 --sh - Different argument switches (/, - or --) can be used in any combination 98 | powerping google.com /ti Paranoid - Sends using the 'Paranoid' timing option 99 | powerping google.com /ti 1 - Same as above 100 | 101 | ## License 102 | 103 | License under the MIT License: 104 | 105 | Copyright (c) 2024 Matthew Carney 106 | 107 | ### Note: 108 | **Requires _Elevated Rights (Administrator)_ to Run (more info [here](https://github.com/Killeroo/PowerPing/issues/110))** 109 | 110 | ## Screenshots 111 | 112 | ![alt text](https://user-images.githubusercontent.com/9999745/74611061-8a3f5180-50f0-11ea-978b-c9fe568c1f8c.png "powerping /g 8.8.8.8") 113 | ![alt text](https://user-images.githubusercontent.com/9999745/74611055-87446100-50f0-11ea-81ac-50551f948437.png "powerping /li") 114 | ![alt text](https://user-images.githubusercontent.com/9999745/74611057-88758e00-50f0-11ea-9259-7b1c8ac83e55.png "powerping /requests /random /displaymsg") 115 | ![alt text](https://user-images.githubusercontent.com/9999745/74611059-89a6bb00-50f0-11ea-9ed4-a2ec4f109dab.png "powerping /t /sym 8.8.8.8") 116 | ![alt text](https://user-images.githubusercontent.com/9999745/74611060-8a3f5180-50f0-11ea-839e-65f9cf03f020.png "powerping /t /sym 1 8.8.8.8") 117 | ![alt text](https://user-images.githubusercontent.com/9999745/74611058-890e2480-50f0-11ea-9ddb-ec79ecf9ce5b.png "powerping 8.8.8.8 /random /fts utc /displaymsg /nocolor /ti polite /t") 118 | -------------------------------------------------------------------------------- /run-tests.ps1: -------------------------------------------------------------------------------- 1 | $scriptPath = split-path -parent $MyInvocation.MyCommand.Definition 2 | 3 | function work-out-test-result($test_name, $result_string) 4 | { 5 | Write-Host "$test_name : ".PadRight(25, " ") -NoNewline 6 | 7 | # Work out percentage tests passed 8 | $tests_performed = $result_string.Split("/")[1] 9 | $tests_passed = $result_string.Split("/")[0] 10 | if ($tests_passed -eq 0 -or $tests_performed -eq 0) { 11 | $percent_tests_passed = 0 12 | } else { 13 | $percent_tests_passed = [int] $(([int]$tests_passed / [int]$tests_performed) * 100) 14 | } 15 | 16 | # Draw colour coded percent 17 | $percent_string = "$percent_tests_passed%".PadRight(5, " ") 18 | if ($percent_tests_passed -lt 50) { 19 | Write-Host $percent_string -NoNewline -ForegroundColor Red 20 | } elseif ($percent_tests_passed -lt 80) { 21 | Write-Host $percent_string -NoNewline -ForegroundColor Yellow 22 | } else { 23 | Write-Host $percent_string -NoNewline -ForegroundColor Green 24 | } 25 | 26 | # Draw some bars representing the percentage 27 | for ($x = 0; $x -lt $percent_tests_passed; $x += 10) { 28 | Write-Host "█" -NoNewline 29 | } 30 | Write-Host 31 | } 32 | 33 | ## Build project 34 | Write-Host "============ build project ============" -ForegroundColor Yellow 35 | $buildScriptPath = $scriptPath + "\scripts\build_dotnet_framework.bat" 36 | cmd.exe /C "$buildScriptPath" 37 | if ($LASTEXITCODE -ne 0) { 38 | Write-Warning "Build failed, you will probably see alot of errors" 39 | } 40 | 41 | ## Check architecture of build 42 | Write-Host "============ build architecture check ============" -ForegroundColor Yellow 43 | $powerping_x64_location = (split-path -parent $MyInvocation.MyCommand.Definition).ToString() + "\src\bin\Release\net6.0\PowerPing.exe" 44 | # $powerping_x86_location = (split-path -parent $MyInvocation.MyCommand.Definition).ToString() + "\build\x86\PowerPing.exe" 45 | if ([reflection.assemblyname]::GetAssemblyName($powerping_x64_location).ProcessorArchitecture -eq "Amd64") { 46 | Write-Host("x64 build is correct architecture") -ForegroundColor Green 47 | } else { 48 | Write-Warning("x64 build is not correct architecture. Detected: "+ [reflection.assemblyname]::GetAssemblyName($powerping_x64_location).ProcessorArchitecture) 49 | } 50 | # if ([reflection.assemblyname]::GetAssemblyName($powerping_x86_location).ProcessorArchitecture -eq "X86") { 51 | # Write-Host("x86 build is correct architecture") -ForegroundColor Green 52 | # } else { 53 | # Write-Warning("x86 build is not correct architecture. Detected: "+ [reflection.assemblyname]::GetAssemblyName($powerping_x86_location).ProcessorArchitecture) 54 | # } 55 | 56 | ## Run test scripts 57 | Write-Host 58 | $test_argument_parsing_result = & "$scriptPath\tests\test-argument-parsing.ps1" 59 | Write-Host 60 | $test_lookup_functions_result = & "$scriptPath\tests\test-lookup-functions.ps1" | select -Last 1 61 | & "$scriptPath\tests\test-core-functions.ps1" 62 | 63 | Write-Host 64 | Write-Host "============ test results ============" -ForegroundColor Yellow 65 | work-out-test-result "test-argument-parsing.ps1" $test_argument_parsing_result 66 | work-out-test-result "test-lookup-functions.ps1" $test_lookup_functions_result -------------------------------------------------------------------------------- /scripts/build_core_mono.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -lt 1 ]; then 4 | echo "Not enough arguments: script [ dotnet OR mono ]"; 5 | exit; 6 | else 7 | if [ $1 = "dotnet" ]; then 8 | 9 | dotnet restore "PowerPing.csproj" 10 | 11 | dotnet build "PowerPing.csproj" 12 | 13 | dotnet publish "PowerPing.csproj" -c Release -r win-x64 14 | dotnet publish "PowerPing.csproj" -c Release -r win-x86 15 | dotnet publish "PowerPing.csproj" -c Release -r osx.10.12-x64 16 | dotnet publish "PowerPing.csproj" -c Release -r linux-x64 17 | 18 | elif [ $1 = "mono" ]; then 19 | 20 | # Unstable 21 | 22 | nuget restore ..\..\PowerPing.sln 23 | 24 | xbuild /p:Configuration=Release ..\..\PowerPing.sln 25 | 26 | fi 27 | fi -------------------------------------------------------------------------------- /scripts/build_dotnet_core.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | cd ..\src\PowerPing 4 | 5 | dotnet build 6 | dotnet publish -c Release -r win-x64 7 | dotnet publish -c Release -r win-x86 8 | dotnet publish -c Release -r osx-x64 9 | dotnet publish -c Release -r linux-x64 10 | dotnet publish -c Release -r debian-x64 11 | dotnet publish -c Release -r ubuntu-x64 12 | :: dotnet publish -c Release -r ubuntu.18.04-x64 13 | :: dotnet publish -c Release -r debian.9-x64 14 | :: dotnet publish -c Release -r osx.10.12-x64 15 | :: dotnet publish -c Release -r osx.10.13-x64 16 | :: dotnet publish -c Release -r win10-x64 17 | :: dotnet publish -c Release -r win10-x86 18 | :: dotnet publish -c Release -r win10-arm 19 | :: dotnet publish -c Release -r win7-x64 20 | :: dotnet publish -c Release -r win7-x86 21 | :: dotnet publish -c Release -r win8-x64 22 | :: dotnet publish -c Release -r win81-x64 23 | pause -------------------------------------------------------------------------------- /scripts/build_dotnet_framework.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | :: Set directory to location of the script 4 | cd "%~dp0" 5 | 6 | :: Arguments check 7 | ::if [%~1]==[] ( 8 | :: echo. 9 | :: echo ERROR: Not enough Arguments 10 | :: echo USAGE: build.bat [C:\path\to\project] 11 | :: exit /b 1 12 | ::) 13 | ::set projectPath=%~f1 14 | 15 | :: Find appropriate msbuild path using vswhere 16 | for /f "usebackq tokens=*" %%A in (`vswhere -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe`) do SET msbuild_path=%%A 17 | 18 | if "%msbuild_path%"=="" GOTO MSBUILD_NOT_FOUND 19 | 20 | :: Run build command 21 | call "%msbuild_path%" ..\PowerPing.sln /p:Configuration=Release /p:Platform="Any CPU" /t:rebuild 22 | if errorlevel 1 GOTO BUILD_FAILED 23 | goto:eof 24 | 25 | :MSBUILD_NOT_FOUND 26 | echo. 27 | echo ERROR: Msbuild could not be found, cannot build project 28 | exit /B 1 29 | GOTO:eof 30 | 31 | :BUILD_FAILED 32 | echo. 33 | echo ERROR: Msbuild failed, fix those errors 34 | exit /B 1 35 | GOTO:eof -------------------------------------------------------------------------------- /scripts/vswhere.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Killeroo/PowerPing/23dd79847dc44f15081e6893f88adac7594ccd96/scripts/vswhere.exe -------------------------------------------------------------------------------- /src/Commands/Flood.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | using System.Net.Sockets; 8 | 9 | namespace PowerPing 10 | { 11 | /// 12 | /// ICMP Flood functionality. 13 | /// 14 | /// Sends a high volume of ICMP pings to a given address 15 | /// 16 | public class Flood 17 | { 18 | private readonly RateLimiter _displayUpdateLimiter = new RateLimiter(TimeSpan.FromMilliseconds(500)); 19 | private ulong _previousPingsSent = 0; 20 | private string _address = ""; 21 | 22 | public void Start(string address, CancellationToken cancellationToken) 23 | { 24 | PingAttributes attrs = new PingAttributes(); 25 | _address = address; 26 | 27 | // Verify address 28 | attrs.ResolvedAddress = Lookup.QueryDNS(address, AddressFamily.InterNetwork); 29 | 30 | // Setup ping attributes 31 | attrs.Interval = 0; 32 | attrs.Timeout = 100; 33 | attrs.Message = "R U Dead Yet?"; 34 | // TODO: Option for 'heavy' flood with bloated packet sizes 35 | attrs.Continous = true; 36 | 37 | // Setup ping object 38 | Ping p = new Ping(attrs, cancellationToken); 39 | p.OnResultsUpdate += OnResultsUpdate; 40 | 41 | // Start flooding 42 | PingResults results = p.Send(); 43 | 44 | ConsoleDisplay.PingResults(attrs, results); 45 | } 46 | 47 | // This callback will run after each ping iteration 48 | public void OnResultsUpdate(PingResults r) 49 | { 50 | // Make sure we're not updating the display too frequently 51 | if (!_displayUpdateLimiter.RequestRun()) 52 | { 53 | return; 54 | } 55 | 56 | // Calculate pings per second 57 | double pingsPerSecond = 0; 58 | if (_displayUpdateLimiter.ElapsedSinceLastRun != TimeSpan.Zero) 59 | { 60 | pingsPerSecond = (r.Sent - _previousPingsSent) / _displayUpdateLimiter.ElapsedSinceLastRun.TotalSeconds; 61 | } 62 | _previousPingsSent = r.Sent; 63 | 64 | // Update results text 65 | ConsoleDisplay.FloodProgress(r.Sent, (ulong)Math.Round(pingsPerSecond), _address); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/Commands/Graph.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | namespace PowerPing 8 | { 9 | /// 10 | /// Graph class, sends pings using Ping.cs and displays on 11 | /// console based graph. 12 | /// 13 | internal class Graph 14 | { 15 | // Constants 16 | private const string FULL_BAR_BLOCK_CHAR = "█"; 17 | 18 | private const string HALF_BAR_BLOCK_CHAR = "▄"; 19 | private const string BOTTOM_BAR_BLOCK_CHAR = "▀"; 20 | 21 | // Properties 22 | public int EndCursorPosY { get; set; } = 0; // Position to move cursor to when graph exits 23 | 24 | // Local variable declaration 25 | private readonly CancellationToken _cancellationToken; 26 | private readonly Ping _ping; 27 | private readonly PingAttributes _pingAttributes = new PingAttributes(); 28 | private readonly List _columns = new List(); 29 | private readonly List _responseTimes = new List(); 30 | 31 | private bool _isGraphSetup = false; 32 | private int _yAxisLength = 20; 33 | private int _xAxisLength = 40; 34 | private int _yAxisLeftPadding = 14; 35 | private int _legendLeftPadding = 16; 36 | private int _startScale = 5; // Stops graph from scaling in past its start scale 37 | private int _scale = 5;//50; // Our current yaxis graph scale 38 | private double _lastAvg = 0; 39 | private double _lastRes = 0; 40 | 41 | // Limits refreshing display too quickly 42 | // NOTE: The actual display update rate may be limited by the ping interval 43 | private readonly RateLimiter _displayUpdateLimiter = new RateLimiter(TimeSpan.FromMilliseconds(500)); 44 | 45 | // Graph positioning and properties 46 | private readonly int _normalLegendLeftPadding = 13; 47 | private readonly int _normalYAxisLeftPadding = 11; 48 | private readonly int _normalYAxisLength = 20; 49 | private readonly int _normalXAxisLength = 70; 50 | 51 | // Location of graph plotting space 52 | private int _plotStartX; 53 | private int _plotStartY; 54 | 55 | // Label locations 56 | private int _sentLabelX, _sentLabelY; 57 | 58 | private int _recvLabelX, _recvLabelY; 59 | private int _failLabelX, _failLabelY; 60 | private int _rttLabelX, _rttLabelY; 61 | private int _timeLabelX, _timeLabelY; 62 | private int _avgLabelX, _avgLabelY; 63 | private int _peakLabelX, _peakLabelY; 64 | private int _yAxisStart; 65 | 66 | public Graph(string address, CancellationToken cancellationTkn) 67 | { 68 | // Setup ping attributes 69 | _pingAttributes.InputtedAddress = address; 70 | _pingAttributes.Continous = true; 71 | 72 | _cancellationToken = cancellationTkn; 73 | _ping = new Ping(_pingAttributes, cancellationTkn); 74 | _ping.OnResultsUpdate += OnPingResultsUpdateCallback; 75 | _scale = _startScale; 76 | } 77 | 78 | /// 79 | /// Draws and sets up graph when it is first run 80 | /// 81 | public void Start() 82 | { 83 | // Hide cursor 84 | Console.CursorVisible = false; 85 | 86 | // Check graph is setup 87 | if (!_isGraphSetup) 88 | { 89 | Setup(); 90 | } 91 | 92 | // Start pinging (initates update loop in OnPingResultsUpdateCallback) 93 | _ping.Send(); 94 | 95 | // Show cursor 96 | Console.CursorVisible = true; 97 | } 98 | 99 | /// 100 | /// Setup graph 101 | /// 102 | private void Setup() 103 | { 104 | // Setup graph properties 105 | _yAxisLength = _normalYAxisLength; 106 | _xAxisLength = _normalXAxisLength; 107 | _legendLeftPadding = _normalLegendLeftPadding; 108 | _yAxisLeftPadding = _normalYAxisLeftPadding; 109 | 110 | CheckAndResizeGraph(); 111 | DrawBackground(); 112 | DrawYAxisLabels(); 113 | 114 | _isGraphSetup = true; 115 | } 116 | 117 | /// 118 | /// Clear the plotting area of the graph 119 | /// 120 | private void Clear() 121 | { 122 | // save cursor location 123 | int cursorPositionX = Console.CursorLeft; 124 | int cursorPositionY = Console.CursorTop; 125 | 126 | // Set cursor position to start of plot 127 | Console.SetCursorPosition(_plotStartX, _yAxisStart);//m_PlotStartY); 128 | 129 | string blankRow = new string(' ', _xAxisLength); 130 | string bottomRow = new string('─', _xAxisLength); 131 | 132 | for (int x = 0; x <= _yAxisLength; x++) 133 | { //21; x++) { 134 | // Draw black spaces 135 | Console.Write(blankRow); 136 | Console.CursorLeft = _plotStartX; 137 | Console.CursorTop = _plotStartY - x; 138 | } 139 | 140 | // Draw bottom row 141 | Console.CursorTop = _plotStartY; 142 | Console.Write(bottomRow); 143 | 144 | // Reset cursor to starting position 145 | Console.SetCursorPosition(cursorPositionX, cursorPositionY); 146 | } 147 | 148 | // This callback will run after each ping iteration 149 | // This is technically our main update loop for the graph 150 | private void OnPingResultsUpdateCallback(PingResults r) 151 | { 152 | // Make sure we're not updating the display too frequently 153 | if (!_displayUpdateLimiter.RequestRun()) 154 | { 155 | return; 156 | } 157 | 158 | int scalePrevious = _scale; 159 | 160 | // Reset position 161 | Console.CursorTop = _plotStartY; 162 | Console.CursorLeft = _plotStartX; 163 | 164 | // Update labels 165 | UpdateLegend(r); 166 | 167 | // Get results from ping and add to graph 168 | AddResponseToGraph(r.CurrTime); 169 | 170 | // Draw graph columns 171 | DrawColumns(); 172 | 173 | // Only draw the y axis labels if the scale has changed 174 | if (scalePrevious != _scale) 175 | { 176 | DrawYAxisLabels(); 177 | } 178 | 179 | Console.CursorTop = EndCursorPosY; 180 | } 181 | 182 | /// 183 | /// Checks if the graph Y axis need to be scaled down or up 184 | /// Scaling is linear, so scale doubles or halves each time. 185 | /// I couldn't be asked to try and hack exponential scaling here 186 | /// althrough it would probably work better 187 | /// 188 | private void CheckGraphScale(double newResponseTime) 189 | { 190 | int newTime = Convert.ToInt32(newResponseTime); 191 | int maxTime = _scale * _yAxisLength; 192 | 193 | // Did we exceed our current scale? 194 | if (newTime > maxTime) 195 | { 196 | _scale *= 2; // Expand! 197 | 198 | // Recurse back into ourself to check the scale again 199 | // Just in case we have to increase the scale all at once 200 | // we want to do it now, instead we will have a jumpy 201 | // rescaling look over the new few bars 202 | CheckGraphScale(newResponseTime); 203 | } 204 | 205 | // Check if any value on the graph is larger than half our current 206 | // max y axis value 207 | bool scaleDown = true; 208 | foreach (double responseTime in _responseTimes) 209 | { 210 | int time = Convert.ToInt32(responseTime); 211 | 212 | if (time > maxTime / 2) 213 | { 214 | scaleDown = false; 215 | } 216 | } 217 | 218 | // If so scale down 219 | if (scaleDown && _scale != _startScale) 220 | { 221 | _scale /= 2; 222 | } 223 | } 224 | 225 | /// 226 | /// Checks the current console window size and adjusts the graph if 227 | /// required. 228 | /// 229 | private void CheckAndResizeGraph() 230 | { 231 | if (Console.WindowWidth < _xAxisLength + _yAxisLeftPadding) 232 | { 233 | _xAxisLength = Math.Max(Console.WindowWidth - _yAxisLeftPadding - 5, 35); 234 | } 235 | if (Console.WindowHeight < _yAxisLength) 236 | { 237 | _yAxisLength = Math.Max(Console.WindowHeight - 5, 10); 238 | } 239 | } 240 | 241 | /// 242 | /// Add a column to the graph list 243 | /// 244 | private void AddResponseToGraph(double responseTime) 245 | { 246 | _responseTimes.Add(responseTime); 247 | 248 | // If number of columns exceeds x Axis length 249 | if (_responseTimes.Count >= _xAxisLength) 250 | { 251 | // Remove first element 252 | _responseTimes.RemoveAt(0); 253 | } 254 | } 255 | 256 | /// 257 | /// Draw all graph coloums/bars 258 | /// 259 | private void DrawColumns() 260 | { 261 | // Clear columns space before drawing 262 | Clear(); 263 | 264 | for (int x = 0; x < _responseTimes.Count; x++) 265 | { 266 | // This causes us to draw a continous lower line of red when we are continously timing out 267 | // Instead of always drawing big red lines, we draw them at either end of the continous zone 268 | // I think it will just look nicer, it will cause slightly hackier code but oh well 269 | bool drawTimeoutSegment = false; 270 | 271 | // Column type 272 | bool timeout = false; 273 | bool current = false; 274 | 275 | if (x == _responseTimes.Count - 1) 276 | { 277 | current = true; 278 | } 279 | if (_responseTimes[x] == 0) 280 | { 281 | timeout = true; 282 | } 283 | 284 | // So to get a timeout segment we peak at the elements ahead and behind to check they are timeouts 285 | // if not we will just draw a normal line at the end of the timeout 286 | // Horrible hacky inline logic to make sure we don't outofbounds while checking behind and head in the array 287 | if (_responseTimes[x] == 0 288 | && (x != 0 ? _responseTimes[x - 1] == 0 : false) 289 | && ((x < _responseTimes.Count - 1 ? _responseTimes[x + 1] == 0 : false) || x == _responseTimes.Count - 1)) 290 | { 291 | drawTimeoutSegment = true; 292 | } 293 | 294 | DrawSingleColumn(CreateColumn(_responseTimes[x]), current, timeout, drawTimeoutSegment); 295 | 296 | Console.CursorLeft++; 297 | } 298 | 299 | // Reset colour after 300 | Console.ForegroundColor = ConsoleColor.Gray; 301 | } 302 | 303 | /// 304 | /// Generate bar for graph 305 | /// 306 | /// Reply time of packet to plot 307 | private string[] CreateColumn(double replyTime) 308 | { 309 | string[] bar; 310 | int count = 0; 311 | int time = Convert.ToInt32(replyTime); 312 | 313 | // Work out bar length 314 | for (int x = 0; x < time; x = x + _scale) 315 | { 316 | count++; 317 | } 318 | 319 | // Scale up or down graph as needed 320 | CheckGraphScale(replyTime); 321 | 322 | if (time > _scale * _yAxisLength) 323 | { 324 | // If reply time over graph Y range draw max size column 325 | count = 10; 326 | } 327 | else if (time == 0) 328 | { 329 | // If no reply dont draw column 330 | string[] timeoutBar = new string[_yAxisLength + 1]; 331 | for (int x = 0; x < timeoutBar.Length; x++) 332 | { 333 | timeoutBar[x] = "|"; 334 | } 335 | timeoutBar[0] = "┴"; 336 | return timeoutBar; 337 | } 338 | 339 | // Create array to store bar 340 | bar = new string[count + 1]; 341 | 342 | // Fill bar 343 | for (int x = 0; x < count; x = x + 1) 344 | { // count + 1 345 | bar[x] = FULL_BAR_BLOCK_CHAR; 346 | } 347 | 348 | // Replace lowest bar segment 349 | bar[0] = "▀"; 350 | 351 | // Replace the last segment 352 | // https://stackoverflow.com/a/2705553 353 | int nearestMultiple = (int)Math.Round((time / (double)_scale), MidpointRounding.AwayFromZero) * _scale; 354 | 355 | if (nearestMultiple - time < 0) 356 | { 357 | bar[bar.Length - 1] = " "; 358 | } 359 | else 360 | { 361 | bar[bar.Length - 1] = HALF_BAR_BLOCK_CHAR; 362 | } 363 | 364 | return bar; 365 | } 366 | 367 | /// 368 | /// Draw graph background 369 | /// 370 | private void DrawBackground() 371 | { 372 | // Draw title 373 | Console.WriteLine(); 374 | 375 | // Save position for later 376 | _yAxisStart = Console.CursorTop; 377 | 378 | // Draw graph y axis 379 | // Hack: this is code that I copied from the y axis generation function 380 | int maxLines = _yAxisLength; 381 | int maxYValue = maxLines * _scale; 382 | int currValue = maxYValue; 383 | for (int x = maxLines; x != 0; x--) 384 | { 385 | // write current value with m_LegendLeftPadding (slightly less every 2 lines) 386 | if (x % 2 == 0) 387 | { 388 | Console.Write(currValue.ToString().PadLeft(_yAxisLeftPadding) + " "); 389 | } 390 | else 391 | { 392 | Console.Write(new string(' ', _yAxisLeftPadding + 1)); 393 | } 394 | 395 | // Add indentation every 2 lines 396 | if (x % 2 == 0) 397 | { 398 | Console.Write("─"); 399 | } 400 | else 401 | { 402 | Console.Write(" "); 403 | } 404 | 405 | if (x == maxLines) 406 | { 407 | Console.WriteLine("┐"); 408 | } 409 | else 410 | { 411 | Console.WriteLine("┤"); 412 | } 413 | 414 | currValue -= _scale; 415 | } 416 | 417 | // Draw X axis of graph 418 | Console.Write(new string(' ', _yAxisLeftPadding) + "0 └"); 419 | 420 | // Save start of graph plotting area 421 | _plotStartX = Console.CursorLeft; 422 | _plotStartY = Console.CursorTop; 423 | Console.WriteLine(new string('─', _xAxisLength)); 424 | Console.WriteLine(); 425 | 426 | string leftPadding = new string(' ', _legendLeftPadding); 427 | 428 | // Draw info (and get location info for each label) 429 | Console.WriteLine(leftPadding + " Ping Statistics:"); 430 | Console.WriteLine(leftPadding + "-----------------------------------"); 431 | Console.WriteLine(leftPadding + " Destination [ {0} ]", _pingAttributes.InputtedAddress); 432 | 433 | Console.Write(leftPadding + " Sent: "); 434 | _sentLabelX = Console.CursorLeft; 435 | _sentLabelY = Console.CursorTop; 436 | 437 | Console.ForegroundColor = ConsoleColor.Green; 438 | Console.Write(" Received: "); 439 | _recvLabelX = Console.CursorLeft; 440 | _recvLabelY = Console.CursorTop; 441 | Console.ForegroundColor = ConsoleColor.Gray; 442 | 443 | Console.ForegroundColor = ConsoleColor.Yellow; 444 | Console.Write(" Average: "); 445 | _avgLabelX = Console.CursorLeft; 446 | _avgLabelY = Console.CursorTop; 447 | Console.WriteLine(); 448 | Console.ForegroundColor = ConsoleColor.Gray; 449 | 450 | Console.Write(leftPadding + " Current: "); 451 | _rttLabelX = Console.CursorLeft; 452 | _rttLabelY = Console.CursorTop; 453 | 454 | Console.ForegroundColor = ConsoleColor.Red; 455 | Console.Write(" Lost: "); 456 | _failLabelX = Console.CursorLeft; 457 | _failLabelY = Console.CursorTop; 458 | Console.ForegroundColor = ConsoleColor.Gray; 459 | 460 | Console.ForegroundColor = ConsoleColor.Cyan; 461 | Console.Write(" Peak: "); 462 | _peakLabelX = Console.CursorLeft; 463 | _peakLabelY = Console.CursorTop; 464 | Console.ForegroundColor = ConsoleColor.Gray; 465 | Console.WriteLine(); 466 | 467 | Console.Write(leftPadding + " Time Elapsed: "); 468 | _timeLabelX = Console.CursorLeft; 469 | _timeLabelY = Console.CursorTop; 470 | Console.WriteLine(); 471 | 472 | EndCursorPosY = Console.CursorTop; 473 | } 474 | 475 | /// 476 | /// Draw graph bar 477 | /// 478 | private void DrawSingleColumn(string[] column, bool current, bool timeout, bool timeoutSegment) 479 | { 480 | // save cursor location 481 | int startingCursorPositionX = Console.CursorLeft; 482 | int startingCursorPositionY = Console.CursorTop; 483 | 484 | if (timeoutSegment) 485 | { 486 | Console.Write("─"); 487 | 488 | Console.CursorLeft--; 489 | return; 490 | } 491 | 492 | bool inverting = false; 493 | foreach (string segment in column) 494 | { 495 | // Determine colour of segment 496 | if (timeout) 497 | { 498 | Console.ForegroundColor = ConsoleColor.DarkRed; 499 | } 500 | else if (current) 501 | { 502 | Console.ForegroundColor = ConsoleColor.Green; 503 | } 504 | else if (inverting) 505 | { 506 | Console.ForegroundColor = ConsoleColor.Gray; 507 | inverting = false; 508 | } 509 | else 510 | { 511 | Console.ForegroundColor = ConsoleColor.DarkGray; 512 | inverting = true; 513 | } 514 | 515 | Console.Write(segment); 516 | 517 | // in an attempt to save time by not always accessing 518 | // Console.Cursor positions too many times (doesn't really 519 | // make that much of a difference) 520 | int cursorPositionLeft = Console.CursorLeft; 521 | int cursorPositionTop = Console.CursorTop; 522 | 523 | // Stop over drawing at the top of the graph 524 | if (cursorPositionTop == _yAxisStart) 525 | { 526 | break; 527 | } 528 | 529 | if (cursorPositionTop != 0) 530 | { 531 | cursorPositionTop--; 532 | cursorPositionLeft--; 533 | 534 | Console.SetCursorPosition(cursorPositionLeft, cursorPositionTop); 535 | } 536 | } 537 | 538 | // Reset cursor to starting position 539 | Console.SetCursorPosition(startingCursorPositionX, startingCursorPositionY); 540 | } 541 | 542 | /// 543 | /// Draws the labels for the y axis based on our current m_Scale 544 | /// 545 | public void DrawYAxisLabels() 546 | { 547 | int maxLines = _yAxisLength; 548 | int maxYValue = maxLines * _scale; 549 | 550 | int topStart = Console.CursorTop; 551 | int leftStart = Console.CursorLeft; 552 | 553 | // Setup cursor position for drawing labels 554 | Console.CursorTop = _yAxisStart; 555 | Console.CursorLeft = 0; 556 | 557 | int currValue = maxYValue; 558 | for (int x = maxLines; x != 0; x--) 559 | { 560 | // write current value with padding (slightly less every 2 lines) 561 | if (x % 2 == 0) 562 | { 563 | Console.Write(currValue.ToString().PadLeft(_yAxisLeftPadding) + " "); 564 | } 565 | else 566 | { 567 | Console.Write(new string(' ', _yAxisLeftPadding + 1)); 568 | } 569 | 570 | // Add indentation every 2 lines 571 | if (x % 2 == 0) 572 | { 573 | Console.Write("─"); 574 | } 575 | else 576 | { 577 | Console.Write(" "); 578 | } 579 | 580 | if (x == maxLines) 581 | { 582 | Console.WriteLine("┐"); 583 | } 584 | else 585 | { 586 | Console.WriteLine("┤"); 587 | } 588 | 589 | currValue -= _scale; 590 | } 591 | 592 | // Draw name of y axis 593 | Console.CursorTop = _yAxisStart + maxLines / 2; 594 | Console.CursorLeft = 1; 595 | Console.WriteLine("Round"); 596 | Console.CursorLeft = 2; 597 | Console.WriteLine("Trip"); 598 | Console.CursorLeft = 2; 599 | Console.WriteLine("Time"); 600 | Console.CursorLeft = 2; 601 | Console.WriteLine("(MS)"); 602 | 603 | // Reset cursor position 604 | Console.CursorLeft = leftStart; 605 | Console.CursorTop = topStart; 606 | } 607 | 608 | /// 609 | /// Update graph legend text labels 610 | /// 611 | /// 612 | private void UpdateLegend(PingResults results) 613 | { 614 | // save cursor location 615 | int cursorPositionX = Console.CursorLeft; 616 | int cursorPositionY = Console.CursorTop; 617 | 618 | string blankLabel = new string(' ', 8); 619 | 620 | // Update sent label 621 | Console.SetCursorPosition(_sentLabelX, _sentLabelY); 622 | // Clear label first 623 | Console.Write(blankLabel); 624 | // Move cursor back 625 | Console.CursorLeft = Console.CursorLeft - 8; 626 | // Write label value 627 | Console.Write(results.Sent); 628 | 629 | // Update recieve label 630 | Console.SetCursorPosition(_recvLabelX, _recvLabelY); 631 | Console.Write(blankLabel); 632 | Console.CursorLeft = Console.CursorLeft - 8; 633 | Console.Write(results.Received); 634 | 635 | // Update average label 636 | Console.SetCursorPosition(_avgLabelX, _avgLabelY); 637 | Console.Write(new string(' ', 15)); 638 | Console.CursorLeft = Console.CursorLeft - 15; 639 | double r = Math.Round(results.AvgTime, 1); 640 | if (_lastAvg < r) 641 | { 642 | Console.ForegroundColor = ConsoleColor.DarkRed; 643 | Console.Write("+"); 644 | if (results.CurrTime - _lastRes > 20) 645 | Console.Write("+"); 646 | } 647 | else if (_lastAvg > r) 648 | { 649 | Console.ForegroundColor = ConsoleColor.DarkGreen; 650 | Console.Write("-"); 651 | if (_lastRes - results.CurrTime > 20) 652 | Console.Write("-"); 653 | } 654 | else 655 | { 656 | Console.ForegroundColor = ConsoleColor.DarkYellow; 657 | Console.Write("~"); 658 | } 659 | _lastAvg = r; 660 | _lastRes = results.CurrTime; 661 | Console.ResetColor(); 662 | Console.Write("{0:0.0}ms", results.AvgTime); 663 | 664 | // Update fail label 665 | Console.SetCursorPosition(_failLabelX, _failLabelY); 666 | Console.Write(blankLabel); 667 | Console.CursorLeft = Console.CursorLeft - 8; 668 | Console.Write(results.Lost); 669 | 670 | // Update peak label 671 | Console.SetCursorPosition(_peakLabelX, _peakLabelY); 672 | Console.Write(new string(' ', 15)); 673 | Console.CursorLeft = Console.CursorLeft - 15; 674 | List noTimeoutResponses = new List(); 675 | noTimeoutResponses.AddRange(_responseTimes); 676 | noTimeoutResponses.RemoveAll(x => x == 0d); 677 | Console.Write("{0}ms", 678 | _responseTimes.Count > 0 && noTimeoutResponses.Count > 0 ? Math.Round(noTimeoutResponses.Max(), 1) : 0); 679 | 680 | // Update RTT label 681 | Console.SetCursorPosition(_rttLabelX, _rttLabelY); 682 | Console.Write(blankLabel); 683 | Console.CursorLeft = Console.CursorLeft - 8; 684 | Console.Write("{0:0.0}ms", results.CurrTime); 685 | 686 | // Update time label 687 | Console.SetCursorPosition(_timeLabelX, _timeLabelY); 688 | Console.Write(blankLabel + " "); 689 | Console.CursorLeft = Console.CursorLeft - 16; 690 | Console.Write("{0:hh\\:mm\\:ss}", results.TotalRunTime); 691 | 692 | // Reset cursor to starting position 693 | Console.SetCursorPosition(cursorPositionX, cursorPositionY); 694 | } 695 | } 696 | } -------------------------------------------------------------------------------- /src/Commands/Listen.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | using System.Net; 8 | using System.Net.Sockets; 9 | 10 | namespace PowerPing 11 | { 12 | /// 13 | /// Listens for all ICMPv4 activity on localhost. 14 | /// 15 | /// Does this by setting a raw socket to SV_IO_ALL which 16 | /// will recieve all packets and filters to just show 17 | /// ICMP packets. Runs until ctrl-c or exit 18 | /// 19 | /// https://stackoverflow.com/a/9174392 20 | internal static class Listen 21 | { 22 | private static Thread[]? _listenThreads; 23 | 24 | public static void Start(CancellationToken cancellationToken, string address = "") 25 | { 26 | if (System.Environment.OSVersion.Platform != PlatformID.Win32NT) 27 | { 28 | Helper.ErrorAndExit("Listen mode is not supported on a non windows platform."); 29 | return; 30 | } 31 | 32 | // Look up local addresses to listen on 33 | IPAddress[]? localAddresses; 34 | if (address == "") 35 | { 36 | // If no address is given then listen on all local addresses 37 | localAddresses = GetLocalAddresses(); 38 | 39 | } 40 | else 41 | { 42 | // Otherwise just listen on the address we are given 43 | localAddresses = new IPAddress[] { IPAddress.Parse(address) }; 44 | } 45 | 46 | if (localAddresses == null) 47 | { 48 | Helper.ErrorAndExit("Could not find local addresses to listen on."); 49 | return; 50 | } 51 | 52 | // Start a listening thread for each ipv4 local address 53 | int size = localAddresses.Length; 54 | _listenThreads = new Thread[size]; 55 | for (int x = 0; x < localAddresses.Length; x++) 56 | { 57 | int index = x; 58 | _listenThreads[index] = new(() => 59 | { 60 | ListenForICMPOnAddress(localAddresses[index]); 61 | }); 62 | _listenThreads[index].IsBackground = true; 63 | _listenThreads[index].Start(); 64 | } 65 | 66 | // Wait for cancellation signal 67 | while (true) 68 | { 69 | if (cancellationToken.IsCancellationRequested) 70 | { 71 | return; 72 | } 73 | Thread.Sleep(50); 74 | } 75 | 76 | // TODO: Implement ListenResults method 77 | //Display.ListenResults(results); 78 | } 79 | 80 | private static IPAddress[]? GetLocalAddresses() 81 | { 82 | IPHostEntry hostAddress; 83 | 84 | // Get all addresses assocatied with this computer 85 | try 86 | { 87 | hostAddress = Dns.GetHostEntry(Dns.GetHostName()); 88 | } 89 | catch (Exception e) 90 | { 91 | ConsoleDisplay.Error($"Could not fetch local addresses ({e.GetType().ToString().Split('.').Last()})"); 92 | return null; 93 | } 94 | 95 | // Only get IPv4 address 96 | List addresses = new(); 97 | foreach (IPAddress address in hostAddress.AddressList) 98 | { 99 | if (address.AddressFamily == AddressFamily.InterNetwork) 100 | { 101 | addresses.Add(address); 102 | } 103 | } 104 | 105 | return addresses.ToArray(); 106 | } 107 | 108 | public static void ListenForICMPOnAddress(IPAddress address) 109 | { 110 | Socket listeningSocket; 111 | PingResults results = new(); 112 | int bufferSize = 4096; 113 | 114 | // Create listener socket 115 | try 116 | { 117 | listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp); 118 | listeningSocket.Bind(new IPEndPoint(address, 0)); 119 | #pragma warning disable CA1416 // Validate platform compatibility - We shouldn't be running listen mode on non windows platform 120 | listeningSocket.IOControl(IOControlCode.ReceiveAll, new byte[] { 1, 0, 0, 0 }, new byte[] { 1, 0, 0, 0 }); // Set SIO_RCVALL flag to socket IO control 121 | #pragma warning restore CA1416 // Validate platform compatibility 122 | listeningSocket.ReceiveBufferSize = bufferSize; 123 | } 124 | catch (Exception e) 125 | { 126 | ConsoleDisplay.Error($"Exception occured while trying to create listening socket for {address.ToString()} ({e.GetType().ToString().Split('.').Last()})"); 127 | return; 128 | } 129 | 130 | ConsoleDisplay.ListenIntroMsg(address.ToString()); 131 | 132 | // Listening loop 133 | while (true) 134 | { 135 | byte[] buffer = new byte[bufferSize]; 136 | 137 | try 138 | { 139 | // Recieve any incoming ICMPv4 packets 140 | EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); 141 | int bytesRead = listeningSocket.ReceiveFrom(buffer, ref remoteEndPoint); 142 | IPv4 ipHeader = new IPv4(buffer, bytesRead); 143 | ICMP response = new(buffer, bytesRead, ipHeader.HeaderLength); 144 | string remoteEndPointIp = remoteEndPoint.ToString() ?? string.Empty; 145 | 146 | // Display captured packet 147 | ConsoleDisplay.CapturedPacket(address.ToString(), response, remoteEndPointIp, DateTime.Now.ToString("h:mm:ss.ff tt"), bytesRead); 148 | 149 | // Store results 150 | results.CountPacketType(response.Type); 151 | results.IncrementReceivedPackets(); 152 | } 153 | catch (OperationCanceledException) 154 | { 155 | } 156 | catch (SocketException) 157 | { 158 | ConsoleDisplay.Error("Could not read packet from socket"); 159 | } 160 | } 161 | 162 | // No nice way to cancel from listening and close socket. Just to got to hope/assume runtime does it when the thread is killed. 163 | //listeningSocket.Close(); 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /src/Commands/Lookup.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | using System.Net; 8 | using System.Net.NetworkInformation; 9 | using System.Net.Sockets; 10 | using System.Reflection; 11 | using System.Text; 12 | using System.Text.Json; 13 | using System.Xml; 14 | 15 | namespace PowerPing 16 | { 17 | /// 18 | /// Static function class for methods related to geoip, dnslookup and whois methods 19 | /// 20 | public static class Lookup 21 | { 22 | // Root Whois server to query addresses against 23 | private const string ROOT_WHOIS_SERVER = "whois.iana.org"; 24 | 25 | /// 26 | /// Returns the local IP address 27 | /// 28 | /// There is a currently a bug where the address of a VM interface can be used 29 | /// instead of the actual local address 30 | /// IP address string, if no address found then returns a null 31 | public static string GetLocalAddress() 32 | { 33 | // If not connected to a network return null 34 | if (!NetworkInterface.GetIsNetworkAvailable()) 35 | { 36 | return string.Empty; 37 | } 38 | 39 | // Get all addresses assocatied with this computer 40 | IPHostEntry hostAddress = Dns.GetHostEntry(Dns.GetHostName()); 41 | 42 | // Loop through each associated address 43 | foreach (IPAddress address in hostAddress.AddressList) 44 | { 45 | // If address is IPv4 46 | if (address.AddressFamily == AddressFamily.InterNetwork) 47 | { 48 | return address.ToString(); 49 | } 50 | } 51 | 52 | return string.Empty; 53 | } 54 | 55 | /// 56 | /// Prints location information about IP Address 57 | /// 58 | /// Address to get location info on. Can be in IP or address format. 59 | /// Display detailed or simplified location info 60 | public static string GetAddressLocationInfo(string addr, bool detailed) 61 | { 62 | //using (HttpClient webClient = new()) 63 | //{ 64 | // try 65 | // { 66 | // string jsonResult = string.Empty; 67 | // webClient.DefaultRequestHeaders.Add("User-Agent", "PowerPing (version_check)"); 68 | // webClient.BaseAddress = new Uri(@"http://api.github.com/repos/killeroo/powerping/releases/latest"); 69 | // webClient.GetStringAsync(jsonResult).GetAwaiter().GetResult(); 70 | 71 | // Dictionary? jsonTable = JsonSerializer.Deserialize>(jsonResult); 72 | 73 | // // Extract version from returned json 74 | // if (jsonTable.ContainsKey("tag_name")) 75 | // { 76 | // string matchString = jsonTable["tag_name"]; 77 | // Version theirVersion = new(matchString.Split(':')[1].Replace("\"", string.Empty).Replace("v", string.Empty)); 78 | // Version? ourVersion = Assembly.GetExecutingAssembly().GetName().Version; 79 | 80 | // if (theirVersion > ourVersion) 81 | // { 82 | // Console.WriteLine(); 83 | // Console.WriteLine("========================================================="); 84 | // Console.WriteLine("A new version of PowerPing is available ({0})", theirVersion); 85 | // Console.WriteLine("Download the new version at: {0}", @"https://github.com/killeroo/powerping/releases/latest"); 86 | // Console.WriteLine("========================================================="); 87 | // Console.WriteLine(); 88 | // } 89 | // } 90 | // } 91 | // catch (Exception) { } // We just want to blanket catch any exception and silently continue 92 | //} 93 | 94 | 95 | string result = ""; 96 | 97 | try 98 | { 99 | using (WebClient objClient = new WebClient()) 100 | { 101 | string key = ""; 102 | 103 | // Download xml data for address 104 | string downloadedText = objClient.DownloadString( 105 | $"http://api.ipstack.com/{addr}?access_key={key}&output=xml"); 106 | 107 | // Load xml file into object 108 | XmlDocument xmlDoc = new(); 109 | xmlDoc.LoadXml(downloadedText); 110 | XmlNodeList elements = (xmlDoc.DocumentElement).ChildNodes; 111 | 112 | if (elements == null) 113 | { 114 | throw new Exception(); 115 | } 116 | 117 | // Print it out 118 | if (detailed) 119 | { 120 | foreach (XmlElement element in elements) 121 | { 122 | if (element.Name != "location") 123 | { 124 | result += element.Name + ": " + (element.InnerText == "" ? "N/A" : element.InnerText) + Environment.NewLine; 125 | } 126 | } 127 | } 128 | else 129 | { 130 | result += "["; 131 | if (elements[7].InnerText != "") 132 | { 133 | result += elements[7].InnerText + ", "; 134 | } 135 | if (elements[4].InnerText != "") 136 | { 137 | result += elements[4].InnerText; 138 | } 139 | result += "]"; 140 | } 141 | } 142 | } 143 | catch (Exception) 144 | { 145 | result = "[Location unavaliable]"; 146 | } 147 | 148 | return result; 149 | } 150 | 151 | /// 152 | /// Resolve address string to IP Address by querying DNS server 153 | /// 154 | /// 155 | /// 156 | /// 157 | public static string QueryDNS(string address, AddressFamily af) 158 | { 159 | IPAddress? ipAddr; 160 | 161 | // Check address format 162 | if (Uri.CheckHostName(address) == UriHostNameType.Unknown) 163 | { 164 | Helper.ErrorAndExit("PowerPing could not resolve host [" + address + "] " + Environment.NewLine + "Check address and try again."); 165 | } 166 | 167 | // Only resolve address if not already in IP address format 168 | if (IPAddress.TryParse(address, out ipAddr)) 169 | { 170 | return ipAddr.ToString(); 171 | } 172 | 173 | try 174 | { 175 | // Query DNS for host address 176 | foreach (IPAddress a in Dns.GetHostEntry(address).AddressList) 177 | { 178 | // Run through addresses until we find one that matches the family we are forcing 179 | if (a.AddressFamily == af) 180 | { 181 | ipAddr = a; 182 | break; 183 | } 184 | } 185 | } 186 | catch (Exception) { } // Silently continue on lookup error 187 | 188 | // If no address resolved then exit 189 | if (ipAddr == null) 190 | { 191 | Helper.ErrorAndExit("PowerPing could not find host [" + address + "] " + Environment.NewLine + "Check address and try again."); 192 | return string.Empty; 193 | } 194 | else 195 | { 196 | return ipAddr.ToString(); 197 | } 198 | } 199 | 200 | /// 201 | /// Performs reverse lookup of host, returning hostname from a given 202 | /// IP address 203 | /// 204 | /// 205 | /// https://stackoverflow.com/a/716753 206 | /// 207 | public static string QueryHost(string address) 208 | { 209 | string alias = ""; 210 | 211 | try 212 | { 213 | IPAddress hostAddr = IPAddress.Parse(address); 214 | IPHostEntry hostInfo = Dns.GetHostEntry(hostAddr); 215 | alias = hostInfo.HostName; 216 | } 217 | catch (Exception) { } // Silently continue on lookup error 218 | 219 | return alias; 220 | } 221 | 222 | /// 223 | /// Internal whois function for recursive lookup 224 | /// 225 | /// 226 | /// 227 | public static void QueryWhoIs(string address, bool full = true) 228 | { 229 | // Trim the inputted address 230 | address = address.Split('/')[0]; 231 | string keyword = address.Split('.')[0]; 232 | string tld = address.Split('.').Last(); 233 | 234 | // Quick sanity check before we proceed 235 | if (keyword == "" || tld == "") 236 | { 237 | Helper.ErrorAndExit("Incorrectly formatted address, please check format and try again"); 238 | } 239 | ConsoleDisplay.Message("WHOIS [" + address + "]:", ConsoleColor.Yellow); 240 | 241 | // Find appropriate whois for the tld 242 | ConsoleDisplay.Message("QUERYING [" + ROOT_WHOIS_SERVER + "] FOR TLD [" + tld + "]:", ConsoleColor.Yellow, false); 243 | string whoisRoot = PerformWhoIsLookup(ROOT_WHOIS_SERVER, tld); 244 | ConsoleDisplay.Message(" DONE", ConsoleColor.Yellow); 245 | if (full) 246 | { 247 | Console.WriteLine(whoisRoot); 248 | } 249 | whoisRoot = whoisRoot.Remove(0, whoisRoot.IndexOf("whois:", StringComparison.Ordinal) + 6).TrimStart(); 250 | whoisRoot = whoisRoot.Substring(0, whoisRoot.IndexOf('\r')); 251 | ConsoleDisplay.Message("QUERYING [" + whoisRoot + "] FOR DOMAIN [" + address + "]:", ConsoleColor.Yellow, false); 252 | 253 | // Next query resulting whois for the domain 254 | string result = PerformWhoIsLookup(whoisRoot, address); 255 | ConsoleDisplay.Message(" DONE", ConsoleColor.Yellow); 256 | Console.WriteLine(result); 257 | ConsoleDisplay.Message("WHOIS LOOKUP FOR [" + address + "] COMPLETE.", ConsoleColor.Yellow); 258 | } 259 | 260 | /// 261 | /// Queries a whois server for information on a server 262 | /// (https://en.wikipedia.org/wiki/WHOIS) 263 | /// 264 | /// Address of whois server to use 265 | /// Query string to send to server 266 | /// http://nathanenglert.com/2015/05/25/creating-an-app-to-find-that-domain-youve-always-wanted/ 267 | /// 268 | private static string PerformWhoIsLookup(string whoisServer, string query) 269 | { 270 | StringBuilder result = new StringBuilder(); 271 | 272 | // Connect to whois server 273 | try 274 | { 275 | using (TcpClient whoisClient = new TcpClient(whoisServer, 43)) 276 | using (NetworkStream netStream = whoisClient.GetStream()) 277 | using (BufferedStream bufferStream = new BufferedStream(netStream)) 278 | { 279 | // Write request to server 280 | StreamWriter sw = new StreamWriter(bufferStream); 281 | sw.WriteLine(query); 282 | sw.Flush(); 283 | 284 | // Read response from server 285 | StreamReader sr = new StreamReader(bufferStream); 286 | while (!sr.EndOfStream) 287 | result.AppendLine(sr.ReadLine()); 288 | } 289 | } 290 | catch (SocketException) 291 | { 292 | result.AppendLine("SocketException: Connection to host failed"); 293 | } 294 | 295 | return result.ToString(); 296 | } 297 | } 298 | } -------------------------------------------------------------------------------- /src/Commands/Scan.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Diagnostics; 10 | using System.Net; 11 | using System.Threading; 12 | using System.Linq; 13 | 14 | namespace PowerPing 15 | { 16 | /// 17 | /// ICMP scanning functionality. 18 | /// 19 | /// Uses pings to scan a IP address range and identify hosts 20 | /// that are active. 21 | /// 22 | /// Range should be in format 192.0.0.1-255, where - denotes the range 23 | /// This can be specified at any octlet of the address (192.0.1-100.1.255) 24 | /// 25 | public static class Scan 26 | { 27 | public static Action? OnScanProgress = null; 28 | public static Action? OnScanFinished = null; 29 | 30 | private const int THREAD_COUNT = 20; 31 | 32 | public struct ProgressEvent 33 | { 34 | public int Scanned; 35 | public int Found; 36 | public int Total; 37 | public int PingsPerSecond; 38 | public TimeSpan CurrentTimestamp; 39 | public string Range; 40 | } 41 | 42 | public struct ResultsEvent 43 | { 44 | public int Scanned; 45 | public bool RanToEnd; 46 | public List Hosts; 47 | } 48 | 49 | /// 50 | /// Used to store information on hosts found in scan 51 | /// 52 | public class HostInformation 53 | { 54 | public string Address { get; set; } 55 | public string HostName { get; set; } 56 | public double ResponseTime { get; set; } 57 | 58 | public HostInformation() 59 | { 60 | Address = string.Empty; 61 | HostName = string.Empty; 62 | } 63 | } 64 | 65 | private static volatile bool _cancelled = false; 66 | 67 | public static void Start(string range, CancellationToken cancellationToken) 68 | { 69 | List addresses = new(); 70 | List activeHosts = new(); 71 | Stopwatch timer = new(); 72 | 73 | // Get addresses to scan from range 74 | addresses = ParseRange(range); 75 | 76 | // Setup addresses and threads 77 | List[] splitAddresses = Helper.PartitionList(addresses, THREAD_COUNT); 78 | Thread[] threads = new Thread[THREAD_COUNT]; 79 | object lockObject = new(); 80 | int scanned = 0; 81 | 82 | // Run the threads 83 | timer.Start(); 84 | for (int i = 0; i < THREAD_COUNT; i++) { 85 | List addrs = splitAddresses[i]; 86 | threads[i] = new Thread(() => { 87 | 88 | PingAttributes attrs = new PingAttributes(); 89 | attrs.InputtedAddress = "127.0.0.1"; 90 | attrs.Timeout = 500; 91 | attrs.Interval = 0; 92 | attrs.Count = 1; 93 | 94 | Ping ping = new Ping(attrs, cancellationToken); 95 | 96 | try { 97 | foreach (string host in addrs) { 98 | 99 | // Send ping 100 | PingResults results = ping.Send(host); 101 | if (results.ScanWasCancelled) { 102 | // Cancel was requested during scan 103 | throw new OperationCanceledException(); 104 | } 105 | 106 | Interlocked.Increment(ref scanned); 107 | 108 | if (results.Lost == 0 && results.ErrorPackets != 1) { 109 | // If host is active, add to list 110 | lock (lockObject) { 111 | activeHosts.Add(new HostInformation { 112 | Address = host, 113 | HostName = "", 114 | ResponseTime = results.CurrTime 115 | }); 116 | } 117 | } 118 | } 119 | } catch (OperationCanceledException) { 120 | _cancelled = true; 121 | } 122 | }); 123 | 124 | threads[i].IsBackground = true; 125 | threads[i].Start(); 126 | } 127 | 128 | // Wait for all threads to exit 129 | int lastSent = 0 , pingsPerSecond = 0; 130 | int lastSpeedCheck = 0; 131 | ProgressEvent progress; 132 | while (threads.Where(x => x.IsAlive).ToList().Count > 0) { 133 | int count = 0; 134 | lock (lockObject) { 135 | count = activeHosts.Count; 136 | } 137 | 138 | if (lastSpeedCheck == 5) { 139 | pingsPerSecond = Math.Abs((scanned - lastSent)); 140 | lastSent = scanned; 141 | lastSpeedCheck = 0; 142 | } 143 | 144 | if (OnScanProgress != null) 145 | { 146 | progress.Scanned = scanned; 147 | progress.Found = activeHosts.Count; 148 | progress.Total = addresses.Count; 149 | progress.PingsPerSecond = pingsPerSecond; 150 | progress.CurrentTimestamp = timer.Elapsed; 151 | progress.Range = range; 152 | 153 | OnScanProgress(progress); 154 | } 155 | 156 | lastSpeedCheck++; 157 | Thread.Sleep(200); 158 | } 159 | 160 | if (OnScanProgress != null) 161 | { 162 | progress.Scanned = scanned; 163 | progress.Found = activeHosts.Count; 164 | progress.Total = addresses.Count; 165 | progress.PingsPerSecond = pingsPerSecond; 166 | progress.CurrentTimestamp = timer.Elapsed; 167 | progress.Range = range; 168 | 169 | // Display one last time so the bar actually completes 170 | // (scan could have completed while the main thread was sleeping) 171 | OnScanProgress(progress); 172 | } 173 | 174 | // Exit out when the operation has been canceled 175 | if (_cancelled && OnScanFinished != null) 176 | { 177 | ResultsEvent results; 178 | results.RanToEnd = false; 179 | results.Scanned = scanned; 180 | results.Hosts = activeHosts; 181 | 182 | OnScanFinished(results); 183 | return; 184 | } 185 | 186 | if (ConsoleDisplay.Configuration.ShowOutput) 187 | ConsoleDisplay.Message("Looking up host names..."); 188 | 189 | // Lookup host names 190 | foreach (HostInformation host in activeHosts) 191 | { 192 | string hostName = Helper.RunWithCancellationToken(() => Lookup.QueryHost(host.Address), cancellationToken); 193 | host.HostName = hostName; 194 | } 195 | 196 | if (OnScanFinished != null) 197 | { 198 | ResultsEvent results; 199 | results.RanToEnd = !cancellationToken.IsCancellationRequested; 200 | results.Scanned = scanned; 201 | results.Hosts = activeHosts; 202 | 203 | OnScanFinished(results); 204 | } 205 | } 206 | 207 | /// 208 | /// Creates an array of all possible ip addresses from range (range format example: 192.168.1-255) 209 | /// 210 | /// 211 | /// 212 | private static List ParseRange(string range) 213 | { 214 | List scanList = new List(); // List of addresses to scan 215 | String[] ipSegments = range.Split('.'); 216 | 217 | // Holds the ranges for each ip segment 218 | int[] segLower = new int[4]; 219 | int[] segUpper = new int[4]; 220 | 221 | // Work out upper and lower ranges for each segment 222 | try { 223 | for (int y = 0; y < 4; y++) { 224 | string[] ranges = ipSegments[y].Split('-'); 225 | segLower[y] = Convert.ToInt16(ranges[0]); 226 | segUpper[y] = (ranges.Length == 1) ? segLower[y] : Convert.ToInt16(ranges[1]); 227 | } 228 | } 229 | catch (FormatException) { 230 | Helper.ErrorAndExit("Scan - Incorrect format [" + range + "], must be specified in format: 192.168.1.1-254"); 231 | } 232 | 233 | // Build list of addresses from ranges 234 | for (int seg1 = segLower[0]; seg1 <= segUpper[0]; seg1++) { 235 | for (int seg2 = segLower[1]; seg2 <= segUpper[1]; seg2++) { 236 | for (int seg3 = segLower[2]; seg3 <= segUpper[2]; seg3++) { 237 | for (int seg4 = segLower[3]; seg4 <= segUpper[3]; seg4++) { 238 | scanList.Add(new IPAddress(new byte[] { (byte)seg1, (byte)seg2, (byte)seg3, (byte)seg4 }).ToString()); 239 | } 240 | } 241 | } 242 | } 243 | 244 | return scanList; 245 | } 246 | 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/Commands/Trace.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace PowerPing 14 | { 15 | public class Trace 16 | { 17 | // Gameplan: 18 | // 1. Ping object in its own thread (send thread) 19 | // 2. A socket setup similarly to Listen.cs (recieve thread) 20 | // 3. 1 ping each, 3 for timeout (option for full trace) 21 | // 4. Display with dns name, ip location and reply time 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Display/ConsoleMessageHandler.cs: -------------------------------------------------------------------------------- 1 | namespace PowerPing 2 | { 3 | public class ConsoleMessageHandler 4 | { 5 | public DisplayConfiguration DisplayConfig = new(); 6 | public PingAttributes Attributes = new(); 7 | public CancellationToken Token = new(); 8 | 9 | public ConsoleMessageHandler(DisplayConfiguration config, CancellationToken token) 10 | { 11 | Attributes = new(); 12 | DisplayConfig = config ?? new DisplayConfiguration(); 13 | Token = token; 14 | } 15 | 16 | public void OnError(PingError error) 17 | { 18 | if (error.Fatal) 19 | { 20 | ConsoleDisplay.Fatal(error.Message, error.Exception); 21 | } 22 | else 23 | { 24 | ConsoleDisplay.Error(error.Message, error.Exception); 25 | } 26 | } 27 | 28 | public void OnFinish(PingResults results) 29 | { 30 | ConsoleDisplay.PingResults(Attributes, results); 31 | } 32 | 33 | public void OnReply(PingReply response) 34 | { 35 | // Determine what form the response address is going to be displayed in 36 | // TODO: Move this when lookup refactor is done 37 | string responseAddress = response.EndpointAddress; 38 | if (responseAddress == string.Empty || DisplayConfig.UseInputtedAddress) 39 | { 40 | if (Attributes != null && Attributes.InputtedAddress != null) 41 | { 42 | responseAddress = Attributes.InputtedAddress; 43 | } 44 | } 45 | else if (DisplayConfig.UseResolvedAddress) 46 | { 47 | string responseIP = responseAddress; 48 | if (responseAddress.Contains(':')) 49 | { 50 | // Returned address normally have port at the end (eg 8.8.8.8:0) so we need to remove that before trying to query the DNS 51 | responseIP = responseAddress.Split(':')[0]; 52 | } 53 | 54 | // Resolve the ip and store as the response address 55 | responseAddress = Helper.RunWithCancellationToken(() => Lookup.QueryHost(responseIP), Token); 56 | } 57 | 58 | ConsoleDisplay.ReplyPacket( 59 | response.Packet, 60 | responseAddress, 61 | response.SequenceNumber, 62 | response.RoundTripTime, 63 | response.TimeToLive, 64 | response.BytesRead); 65 | 66 | if (Attributes != null && 67 | ((Attributes.BeepMode == 2 /* Beep on success */ && response.Packet.Type != 3 /* Host Unreachable packet */ ) || 68 | (Attributes.BeepMode == 1 /* Beep on error */ && response.Packet.Type == 3))) 69 | { 70 | try { Console.Beep(); } 71 | catch (Exception) { } // Silently continue if Console.Beep errors 72 | } 73 | } 74 | 75 | public void OnRequest(PingRequest request) 76 | { 77 | ConsoleDisplay.RequestPacket( 78 | request.Packet, 79 | (DisplayConfig.UseInputtedAddress | DisplayConfig.UseResolvedAddress ? Attributes.InputtedAddress : Attributes.ResolvedAddress), 80 | request.SequenceNumber); 81 | } 82 | 83 | public void OnStart(PingAttributes attributes) 84 | { 85 | // Cache the PingAttributes at this point as Ping has modified the inputted and resolved addresses 86 | Attributes = attributes; 87 | 88 | ConsoleDisplay.PingIntroMsg(attributes); 89 | } 90 | 91 | public void OnTimeout(PingTimeout timeout) 92 | { 93 | ConsoleDisplay.Timeout(timeout.SequenceNumber); 94 | 95 | if (Attributes.BeepMode == 1) 96 | { 97 | try { Console.Beep(); } 98 | catch (Exception) { } 99 | } 100 | } 101 | 102 | internal void OnScanProgress(Scan.ProgressEvent progress) 103 | { 104 | ConsoleDisplay.ScanProgress( 105 | progress.Scanned, 106 | progress.Found, 107 | progress.Total, 108 | progress.PingsPerSecond, 109 | progress.CurrentTimestamp, 110 | progress.Range); 111 | } 112 | 113 | internal void OnScanFinished(Scan.ResultsEvent results) 114 | { 115 | ConsoleDisplay.ScanResults(results.Scanned, results.RanToEnd, results.Hosts); 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /src/Display/DisplayConfiguration.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | namespace PowerPing 8 | { 9 | public class DisplayConfiguration 10 | { 11 | public bool Short { get; set; } = false; 12 | public bool NoColor { get; set; } = false; 13 | public bool UseSymbols { get; set; } = false; 14 | public bool ShowOutput { get; set; } = true; 15 | public bool ShowMessages { get; set; } = false; 16 | public bool ShowTimeStamp { get; set; } = false; 17 | public bool ShowTimeStampUTC { get; set; } = false; 18 | public bool ShowFullTimeStamp { get; set; } = false; 19 | public bool ShowFullTimeStampUTC { get; set; } = false; 20 | public bool ShowTimeouts { get; set; } = true; 21 | public bool ShowRequests { get; set; } = false; 22 | public bool ShowReplies { get; set; } = true; 23 | public bool ShowIntro { get; set; } = true; 24 | public bool ShowSummary { get; set; } = true; 25 | public bool ShowChecksum { get; set; } = false; 26 | public bool UseInputtedAddress { get; set; } = false; 27 | public bool UseResolvedAddress { get; set; } = false; 28 | public bool RequireInput { get; set; } = false; 29 | public int DecimalPlaces { get; set; } = 1; 30 | 31 | public int LowPingThreshold { get; set;} = 100; 32 | public int MidPingThreshold { get; set; } = 250; 33 | public int HighPingThreshold { get; set; } = 500; 34 | } 35 | } -------------------------------------------------------------------------------- /src/Display/Strings.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | namespace PowerPing 8 | { 9 | internal class ProgramStrings 10 | { 11 | // General messages 12 | public const string TIMESTAMP_LAYOUT = " @ {0}"; 13 | public const string FULL_TIMESTAMP_LAYOUT = " @ {0} {1}"; 14 | public const string TIMEOUT_TXT = "Request timed out."; 15 | public const string TIMEOUT_SEQ_TXT = " seq={0}"; 16 | public const string ERROR_TXT = "ERROR: "; 17 | 18 | // Intro messages 19 | public const string INTRO_ADDR_TXT = "Pinging {0} "; 20 | public const string INTRO_MSG = "with {0} bytes of data [Type={1} Code={2}] [TTL={3}]"; 21 | 22 | // Flood messages 23 | public const string FLOOD_INTO_TXT = "Flooding {0}..."; 24 | public const string FLOOD_SEND_TXT = "Sent: "; 25 | public const string FLOOD_PPS_TXT = "Pings per Second: "; 26 | public const string FLOOD_EXIT_TXT = "Press Control-C to stop..."; 27 | 28 | // Listen messages 29 | public const string LISTEN_INTRO_MSG = "Listening for ICMP Packets on [ {0} ]"; 30 | public const string CAPTURED_PACKET_MSG = "{0}: [{5}] ICMPv4: {1} bytes for {2} [type {3}] [code {4}]"; 31 | 32 | // Request messages 33 | public const string REQUEST_MSG = "Request to: {0}:0 seq={1} bytes={2} type="; 34 | public const string REQUEST_MSG_SHORT = "Request to: {0}:0 type="; 35 | public const string REQUEST_CODE_TXT = " code={0}"; 36 | 37 | // Reply messages 38 | public const string REPLY_MSG = "Reply from: {0} seq={1} bytes={2} type="; 39 | public const string REPLY_MSG_SHORT = "Reply from: {0} type="; 40 | public const string REPLY_MSG_TXT = " msg=\"{0}\""; 41 | public const string REPLY_TTL_TXT = " TTL={0}"; 42 | public const string REPLY_CHKSM_TXT = " chksm={0}"; 43 | public const string REPLY_TIME_TXT = " time="; 44 | 45 | // Scan messages 46 | public const string SCAN_RANGE_TXT = "Scanning range [ {0} ]..."; 47 | public const string SCAN_HOSTS_TXT = " Found Hosts: {1} Sent: {0} ({2} Pings Per Second) "; 48 | public const string SCAN_RESULT_MSG = "Scan complete. {0} addresses scanned. {1} hosts active:"; 49 | public const string SCAN_RESULT_ENTRY = "-- {0} [{1:0.0}ms] [{2}]"; 50 | public const string SCAN_CONNECTOR_CHAR = "|"; 51 | public const string SCAN_END_CHAR = @"\"; 52 | 53 | // End results messages 54 | public const string RESULTS_HEADER = "--- Stats for {0} ---"; 55 | public const string RESULTS_GENERAL_TAG = " General: "; 56 | public const string RESULTS_TIMES_TAG = " Times: "; 57 | public const string RESULTS_TYPES_TAG = " Types: "; 58 | public const string RESULTS_SENT_TXT = "Sent "; 59 | public const string RESULTS_RECV_TXT = ", Received "; 60 | public const string RESULTS_LOST_TXT = ", Lost "; 61 | public const string RESULTS_PERCENT_LOST_TXT = " ({0}% loss)"; 62 | public const string RESULTS_PKT_GOOD = "Good "; 63 | public const string RESULTS_PKT_ERR = ", Errors "; 64 | public const string RESULTS_PKT_UKN = ", Unknown "; 65 | public const string RESULTS_TIME_TXT = "Min [ {0:0.0}ms ], Max [ {1:0.0}ms ], Avg [ {2:0.0}ms ]"; 66 | public const string RESULTS_START_TIME_TXT = "Started at: {0} (local time)"; 67 | public const string RESULTS_RUNTIME_TXT = " Runtime: {0:hh\\:mm\\:ss\\.f}"; 68 | public const string RESULTS_INFO_BOX = "[ {0} ]"; 69 | public const string RESULTS_OVERFLOW_MSG = 70 | @"SIDENOTE: I don't know how you've done it but you have caused an overflow somewhere in these ping results. 71 | Just to put that into perspective you would have to be running a normal ping program with default settings for 584,942,417,355 YEARS to achieve this! 72 | Well done brave soul, I don't know your motive but I salute you =^-^="; 73 | 74 | // Characters used in symbol mode 75 | public const string REPLY_LT_100_MS_SYMBOL_1 = "."; 76 | public const string REPLY_LT_250_MS_SYMBOL_1 = "."; 77 | public const string REPLY_LT_500_MS_SYMBOL_1 = "."; 78 | public const string REPLY_GT_500_MS_SYMBOL_1 = "."; 79 | public const string REPLY_TIMEOUT_SYMBOL_1 = "!"; 80 | 81 | public const string REPLY_LT_100_MS_SYMBOL_2 = "_"; 82 | public const string REPLY_LT_250_MS_SYMBOL_2 = "▄"; 83 | public const string REPLY_LT_500_MS_SYMBOL_2 = "█"; 84 | public const string REPLY_GT_500_MS_SYMBOL_2 = "█"; 85 | public const string REPLY_TIMEOUT_SYMBOL_2 = "!"; 86 | 87 | public const string HELP_MSG = 88 | @"__________ __________.__ 89 | \______ \______ _ __ __________\______ \__| ____ ____ 90 | | ___/ _ \ \/ \/ // __ \_ __ \ ___/ |/ \ / ___\ 91 | | | ( <_> ) /\ ___/| | \/ | | | | \/ /_/ > 92 | |____| \____/ \/\_/ \___ >__| |____| |__|___| /\___ / 93 | \/ \//_____/ 94 | 95 | Description: 96 | Advanced ping utility - Provides geoip querying, ICMP packet 97 | customization, graphs and result colourization. 98 | 99 | Ping Arguments: 100 | --infinite [--t] Ping the target until stopped (Ctrl-C to stop) 101 | --ipv4 [--4] Force using IPv4 102 | --random [--rng] Generates random ICMP message 103 | --dontfrag [--df] Set 'Don't Fragment' flag 104 | --buffer [--rb] number Sets recieve buffer size (default is 5096) 105 | --beep [--b] number Beep on timeout (1) or on reply (2) 106 | --count [--c] number Number of pings to send 107 | --timeout [--w] number Time to wait for reply (in milliseconds) 108 | --ttl [--i] number Time To Live for packet 109 | --interval [--in] number Interval between each ping (in milliseconds) 110 | --type [--pt] number Use custom ICMP type 111 | --code [--pc] number Use custom ICMP code value 112 | --size [--s] number Set size (in bytes) of packet (overwrites packet message) 113 | --message [--m] message Ping packet message 114 | --timing [--ti] timing Timing levels: 115 | 0 - Paranoid 4 - Nimble 116 | 1 - Sneaky 5 - Speedy 117 | 2 - Quiet 6 - Insane 118 | 3 - Polite 7 - Random 119 | 120 | Display Arguments: 121 | --shorthand [--sh] Show less detailed replies 122 | --displaymsg [--dm] Display ICMP message field contents 123 | --timestamp [--ts] Display timestamps (add 'UTC' for Coordinated Universal Time) 124 | --fulltimestamp [--fts] Display full timestamps with localised date and time 125 | --nocolor [--nc] No colour 126 | --symbols [--sym] Renders replies and timeouts as ASCII symbols (add '1' for alt theme) 127 | --requests [--r] Show request packets 128 | --notimeouts [--nt] Don't display timeout messages 129 | --quiet [--q] No output (only affects normal ping) 130 | --resolve [--res] Resolve hostname of response address from DNS 131 | --inputaddr [--ia] Show input address instead of revolved IP address 132 | --checksum [--chk] Display checksum of packet 133 | --requireinput [--ri] Always ask for user input upon completion 134 | --limit [--l] number Limits output to just replies(1), requests(2) or summary(3) 135 | --decimals [--dp] number Num of decimal places to use (0 to 3) 136 | --low number Defines the low response time threshold (which times are coloured green) 137 | --mid number Defines the mid response time threshold (which times are coloured yellow) 138 | --high number Defines the high response time threshold (which times are coloured red) 139 | 140 | Modes: 141 | --scan [--sc] address Network scanning, specify range ""127.0.0.1-55"" 142 | --flood [--fl] address Send high volume of pings to address 143 | --graph [--g] address Graph view 144 | --location [--loc] address Location info for an address 145 | --listen [--li] address Listen for ICMP packets on specific address 146 | --listen [--li] Listen for ICMP packets on all local network adapters 147 | --whois address Whois lookup for an address 148 | --whoami Location info for current host 149 | 150 | Other: 151 | --log [--f] path Logs ping output to a file (path is optional) 152 | --help [--?] Displays this help message 153 | --version [--v] Shows version and build information 154 | 155 | Written by Matthew Carney [matthewcarney64@gmail.com] 156 | You can find the project and it's source code here: https://github.com/Killeroo/PowerPing 157 | 158 | "; 159 | 160 | } 161 | 162 | internal static class ICMPStrings 163 | { 164 | // ICMP packet types 165 | public static readonly string[] PacketTypes = new[] 166 | { 167 | "ECHO REPLY", /* 0 */ 168 | "[1] UNASSIGNED [RESV]", /* 1 */ 169 | "[2] UNASSIGNED [RESV]", /* 2 */ 170 | "DESTINATION UNREACHABLE", /* 3 */ 171 | "SOURCE QUENCH [DEPR]", /* 4 */ 172 | "PING REDIRECT", /* 5 */ 173 | "ALTERNATE HOST ADDRESS [DEPR]", /* 6 */ 174 | "[7] UNASSIGNED [RESV]", /* 7 */ 175 | "ECHO REQUEST", /* 8 */ 176 | "ROUTER ADVERTISEMENT", /* 9 */ 177 | "ROUTER SOLICITATION", /* 10 */ 178 | "TIME EXCEEDED", /* 11 */ 179 | "PARAMETER PROBLEM", /* 12 */ 180 | "TIMESTAMP REQUEST", /* 13 */ 181 | "TIMESTAMP REPLY", /* 14 */ 182 | "INFORMATION REQUEST [DEPR]", /* 15 */ 183 | "INFORMATION REPLY [DEPR]", /* 16 */ 184 | "ADDRESS MASK REQUEST [DEPR]", /* 17 */ 185 | "ADDRESS MASK REPLY [DEPR]", /* 18 */ 186 | "[19] SECURITY [RESV]", /* 19 */ 187 | "[20] ROBUSTNESS EXPERIMENT [RESV]", /* 20 */ 188 | "[21] ROBUSTNESS EXPERIMENT [RESV]", /* 21 */ 189 | "[22] ROBUSTNESS EXPERIMENT [RESV]", /* 22 */ 190 | "[23] ROBUSTNESS EXPERIMENT [RESV]", /* 23 */ 191 | "[24] ROBUSTNESS EXPERIMENT [RESV]", /* 24 */ 192 | "[25] ROBUSTNESS EXPERIMENT [RESV]", /* 25 */ 193 | "[26] ROBUSTNESS EXPERIMENT [RESV]", /* 26 */ 194 | "[27] ROBUSTNESS EXPERIMENT [RESV]", /* 27 */ 195 | "[28] ROBUSTNESS EXPERIMENT [RESV]", /* 28 */ 196 | "[29] ROBUSTNESS EXPERIMENT [RESV]", /* 29 */ 197 | "TRACEROUTE [DEPR]", /* 30 */ 198 | "DATAGRAM CONVERSATION ERROR [DEPR]", /* 31 */ 199 | "MOBILE HOST REDIRECT [DEPR]", /* 32 */ 200 | "WHERE-ARE-YOU [DEPR]", /* 33 */ 201 | "HERE-I-AM [DEPR]", /* 34 */ 202 | "MOBILE REGISTRATION REQUEST [DEPR]", /* 35 */ 203 | "MOBILE REGISTRATION REPLY [DEPR]", /* 36 */ 204 | "DOMAIN NAME REQUEST [DEPR]", /* 37 */ 205 | "DOMAIN NAME REPLY [DEPR]", /* 38 */ 206 | "SKIP DISCOVERY PROTOCOL [DEPR]", /* 39 */ 207 | "PHOTURIS PROTOCOL", /* 40 */ 208 | "EXPERIMENTAL MOBILITY PROTOCOLS", /* 41 */ 209 | "[42] UNASSIGNED [RESV]" /* 42+ */ 210 | }; 211 | 212 | // Type specific code values 213 | public static readonly string[] DestinationUnreachableCodeValues = new[] 214 | { 215 | "NETWORK UNREACHABLE", 216 | "HOST UNREACHABLE", 217 | "PROTOCOL UNREACHABLE", 218 | "PORT UNREACHABLE", 219 | "FRAGMENTATION NEEDED & DF FLAG SET", 220 | "SOURCE ROUTE FAILED", 221 | "DESTINATION NETWORK UNKOWN", 222 | "DESTINATION HOST UNKNOWN", 223 | "SOURCE HOST ISOLATED", 224 | "COMMUNICATION WITH DESTINATION NETWORK PROHIBITED", 225 | "COMMUNICATION WITH DESTINATION NETWORK PROHIBITED", 226 | "NETWORK UNREACHABLE FOR ICMP", 227 | "HOST UNREACHABLE FOR ICMP" 228 | }; 229 | public static readonly string[] RedirectCodeValues = new[] 230 | { 231 | "REDIRECT FOR THE NETWORK", 232 | "REDIRECT FOR THE HOST", 233 | "REDIRECT FOR THE TOS & NETWORK", 234 | "REDIRECT FOR THE TOS & HOST" 235 | }; 236 | public static readonly string[] TimeExceedCodeValues = new[] 237 | { 238 | "TTL EXPIRED IN TRANSIT", 239 | "FRAGMENT REASSEMBLY TIME EXCEEDED" 240 | }; 241 | public static readonly string[] BadParameterCodeValues = new[] 242 | { 243 | "IP HEADER POINTER INDICATES ERROR", 244 | "IP HEADER MISSING AN OPTION", 245 | "BAD IP HEADER LENGTH" 246 | }; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/File/LogFile.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | using System.Text; 8 | 9 | namespace PowerPing 10 | { 11 | internal class LogFile : IDisposable 12 | { 13 | private FileStream? _fileStream; 14 | private ASCIIEncoding _asciiEncoder; 15 | private string _filePath; 16 | 17 | public LogFile(string logPath) 18 | { 19 | _asciiEncoder = new ASCIIEncoding(); 20 | _filePath = logPath; 21 | 22 | Create(logPath); 23 | } 24 | 25 | public void Create(string filePath) 26 | { 27 | string? path = ""; 28 | try 29 | { 30 | if (filePath.Contains(Path.DirectorySeparatorChar)) 31 | { 32 | // Check the directory we want to write to exits 33 | path = Path.GetDirectoryName(filePath); 34 | if (path != null && !Directory.Exists(path)) 35 | { 36 | Directory.CreateDirectory(path); 37 | } 38 | } 39 | } 40 | catch (Exception e) 41 | { 42 | ConsoleDisplay.Error($"Cannot create file at {path} changing to {Directory.GetCurrentDirectory()}", e); 43 | 44 | // Change file to be written to current directory 45 | // when we can't create our first directory choice 46 | filePath = Path.Combine(Directory.GetCurrentDirectory(), Path.GetFileName(filePath)); 47 | } 48 | 49 | try 50 | { 51 | // Create the file 52 | _fileStream = File.Create(filePath); 53 | } 54 | catch (Exception ex) 55 | { 56 | ConsoleDisplay.Error($"Cannot write to log file ({filePath})", ex); 57 | _fileStream = null; 58 | } 59 | } 60 | 61 | public void Append(string line) 62 | { 63 | if (_fileStream != null && _fileStream.CanWrite) 64 | { 65 | _fileStream.Write(_asciiEncoder.GetBytes(line + Environment.NewLine)); 66 | 67 | try 68 | { 69 | _fileStream.Flush(); 70 | } 71 | catch (Exception ex) 72 | { 73 | ConsoleDisplay.Error($"Error writing to log file ({_filePath})", ex); 74 | } 75 | } 76 | } 77 | 78 | public void Dispose() 79 | { 80 | _fileStream?.Close(); 81 | } 82 | 83 | public static string SetupPath(string inputtedPath, string address) 84 | { 85 | if (string.IsNullOrEmpty(inputtedPath)) 86 | { 87 | // Generate a filename if we were given nothing 88 | inputtedPath = LogFile.GenerateFileName(address); 89 | } 90 | 91 | if (!Path.HasExtension(inputtedPath)) 92 | { 93 | // Add a file to any path that we were given 94 | inputtedPath = Path.Combine(inputtedPath, LogFile.GenerateFileName(address)); 95 | } 96 | 97 | return Helper.CheckForDuplicateFile(inputtedPath); 98 | } 99 | 100 | public static string GenerateFileName(string address) 101 | { 102 | DateTime logCreationTime = DateTime.Now; 103 | 104 | return string.Format("PowerPing_{0}_{1}{2}{3}_{4}{5}.txt", 105 | address, 106 | logCreationTime.Year, 107 | logCreationTime.Month, 108 | logCreationTime.Day, 109 | logCreationTime.Hour, 110 | logCreationTime.Minute); 111 | } 112 | 113 | } 114 | } -------------------------------------------------------------------------------- /src/File/LogMessageHandler.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | using System; 8 | 9 | namespace PowerPing 10 | { 11 | internal class LogMessageHandler : IDisposable 12 | { 13 | private const string DATETIME_STRING_FORMAT = "yyyy-MM-dd HH:mm:ss.fffzzzzz"; 14 | 15 | private LogFile _logFile; 16 | private DisplayConfiguration _displayConfiguration; 17 | private string _destinationAddress = ""; 18 | 19 | public LogMessageHandler(string logPath, DisplayConfiguration displayConfiguration) 20 | { 21 | _logFile = new LogFile(logPath); 22 | _displayConfiguration = displayConfiguration; 23 | } 24 | ~LogMessageHandler() 25 | { 26 | Dispose(); 27 | } 28 | 29 | internal void OnStart(PingAttributes attributes) 30 | { 31 | _destinationAddress = attributes.InputtedAddress; 32 | string message = attributes.Message.Length > 50 ? $"{attributes.Message.Substring(0, 50)}...({attributes.Message.Length - 50} lines truncated)" : attributes.Message; 33 | 34 | _logFile.Append($"[{_destinationAddress}] " + 35 | $"[{DateTime.Now.ToString(DATETIME_STRING_FORMAT)}] " + 36 | $"[START] " + 37 | $"input_address={attributes.InputtedAddress} " + 38 | $"resolved_address={attributes.ResolvedAddress} " + 39 | $"source_address={attributes.SourceAddress} " + 40 | $"message={message} " + 41 | $"interval={attributes.Interval}ms " + 42 | $"timeout={attributes.Timeout}ms " + 43 | $"count={attributes.Count} " + 44 | $"TTL={attributes.Ttl} " + 45 | $"receive_buffer_size={attributes.ReceiveBufferSize} bytes " + 46 | $"artifical_message_size={attributes.ArtificalMessageSize} bytes " + 47 | $"type={attributes.Type} " + 48 | $"code={attributes.Code} " + 49 | $"beep_mode={attributes.BeepMode} " + 50 | $"continous_mode={attributes.Continous} " + 51 | $"ipv4={attributes.UseICMPv4} " + 52 | $"ipv6={attributes.UseICMPv6} " + 53 | $"random_message={attributes.RandomMessage} " + 54 | $"dont_fragment={attributes.DontFragment} " + 55 | $"random_timing={attributes.RandomTiming} " + 56 | $"enable_logging={attributes.EnableFileLogging} " + 57 | $"log_filename={attributes.LogFilePath}"); 58 | } 59 | 60 | internal void OnFinish(PingResults results) 61 | { 62 | _logFile.Append($"[{_destinationAddress}] " + 63 | $"[{results.EndTime.ToString(DATETIME_STRING_FORMAT)}] " + 64 | $"[FINISH] ellapsed={results.TotalRunTime} start_time={results.StartTime} "); 65 | _logFile.Append($"[{_destinationAddress}] " + 66 | $"[{results.EndTime.ToString(DATETIME_STRING_FORMAT)}] " + 67 | $"[STATISTICS] [PINGS] " + 68 | $"sent={results.Sent} " + 69 | $"received={results.Received} " + 70 | $"lost={results.Lost} "); 71 | _logFile.Append($"[{_destinationAddress}] " + 72 | $"[{results.EndTime.ToString(DATETIME_STRING_FORMAT)}] " + 73 | $"[STATISTICS] [PACKETS] " + 74 | $"good={results.GoodPackets} " + 75 | $"error={results.ErrorPackets} " + 76 | $"other={results.OtherPackets} "); 77 | _logFile.Append($"[{_destinationAddress}] " + 78 | $"[{results.EndTime.ToString(DATETIME_STRING_FORMAT)}] " + 79 | $"[STATISTICS] [TIME] " + 80 | $"max={results.MaxTime} " + 81 | $"min={results.MinTime} " + 82 | $"avg={results.AvgTime} "); 83 | } 84 | 85 | internal void OnTimeout(PingTimeout timeout) 86 | { 87 | _logFile.Append($"[{_destinationAddress}] " + 88 | $"[{timeout.Timestamp.ToString(DATETIME_STRING_FORMAT)}] " + 89 | $"[TIMEOUT] " + 90 | $"seq={timeout.SequenceNumber}"); 91 | } 92 | 93 | internal void OnRequest(PingRequest request) 94 | { 95 | _logFile.Append($"[{_destinationAddress}] " + 96 | $"[{request.Timestamp.ToString(DATETIME_STRING_FORMAT)}] " + 97 | $"[REQUEST] " + 98 | $"seq={request.SequenceNumber} " + 99 | $"bytes={request.PacketSize} " + 100 | $"type={request.Packet.Type} " + 101 | $"code={request.Packet.Code}"); 102 | } 103 | 104 | internal void OnReply(PingReply reply) 105 | { 106 | _logFile.Append($"[{_destinationAddress}] " + 107 | $"[{reply.Timestamp.ToString(DATETIME_STRING_FORMAT)}] " + 108 | $"[REPLY] " + 109 | $"endpoint={reply.EndpointAddress} " + 110 | $"seq={reply.SequenceNumber} " + 111 | $"bytes={reply.BytesRead} " + 112 | $"type={reply.Packet.Type} " + 113 | $"code={reply.Packet.Code} " + 114 | $"ttl={reply.TimeToLive}" + 115 | $"time={reply.RoundTripTime.TotalMilliseconds}ms"); 116 | } 117 | 118 | internal void OnError(PingError error) 119 | { 120 | _logFile.Append($"[{_destinationAddress}]" + 121 | $"[{error.Timestamp.ToString(DATETIME_STRING_FORMAT)}] " + 122 | $"[ERROR] " + 123 | $"message={error.Message} " + 124 | $"error={error.Exception.GetType().Name} "); 125 | } 126 | 127 | internal void OnScanFinished(Scan.ResultsEvent results) 128 | { 129 | _logFile.Append($"[SCAN] " + 130 | $"[{DateTime.Now.ToString(DATETIME_STRING_FORMAT)}] " + 131 | $"Scan {(results.RanToEnd ? "complete" : "aborted")}. {results.Scanned} addresses pinged, {results.Hosts.Count} hosts found."); 132 | 133 | if (results.Hosts.Count > 0) 134 | { 135 | _logFile.Append("[SCAN] Found Hosts:"); 136 | 137 | string hostsList = ""; 138 | for (int i = 0; i < results.Hosts.Count; i++) 139 | { 140 | hostsList += string.Format("[{0}] time={1}ms name={2}" + Environment.NewLine, results.Hosts[i].Address, results.Hosts[i].ResponseTime, results.Hosts[i].HostName != "" ? results.Hosts[i].HostName : "UNAVAILABLE"); 141 | } 142 | _logFile.Append(hostsList); 143 | } 144 | } 145 | 146 | public void Dispose() 147 | { 148 | // Close log file on shutdown 149 | _logFile.Dispose(); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Network/ICMP.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | namespace PowerPing 8 | { 9 | /// 10 | /// ICMP class, for creating Internet Control Message Protocol (ICMP) packet objects 11 | /// 12 | public class ICMP 13 | { 14 | private const int kIcmpHeaderSize = 4; 15 | 16 | public static readonly ICMP EmptyPacket = new() 17 | { 18 | Type = 0, 19 | Code = 0, 20 | Checksum = 0, 21 | MessageSize = 0, 22 | Message = new byte[1024], 23 | }; 24 | 25 | // Packet header 26 | public byte Type; 27 | public byte Code; 28 | public UInt16 Checksum; 29 | public byte[] Message = new byte[1024]; 30 | 31 | public int MessageSize; 32 | 33 | // Constructors 34 | public ICMP() { } 35 | public ICMP(byte[] data, int size, int offset = 20) // Offset first 20 bytes which are the IPv4 header 36 | { 37 | Type = data[offset]; 38 | Code = data[offset + 1]; 39 | Checksum = BitConverter.ToUInt16(data, offset + 2); 40 | MessageSize = size - (offset + kIcmpHeaderSize); 41 | if (MessageSize > Message.Length) 42 | { 43 | Message = new byte[MessageSize]; 44 | } 45 | Buffer.BlockCopy(data, (offset + kIcmpHeaderSize), Message, 0, MessageSize); 46 | } 47 | 48 | /// 49 | /// Convert ICMP packet to byte array 50 | /// 51 | /// Packet in byte array 52 | public byte[] GetBytes() 53 | { 54 | byte[] data = new byte[MessageSize + kIcmpHeaderSize]; 55 | Buffer.BlockCopy(BitConverter.GetBytes(Type), 0, data, 0, 1); 56 | Buffer.BlockCopy(BitConverter.GetBytes(Code), 0, data, 1, 1); 57 | Buffer.BlockCopy(BitConverter.GetBytes(Checksum), 0, data, 2, 2); 58 | Buffer.BlockCopy(Message, 0, data, kIcmpHeaderSize, MessageSize); 59 | return data; 60 | } 61 | 62 | /// 63 | /// Calculate checksum of packet using internet checksum (16bit one's compliment checksum) 64 | /// 65 | /// Packet checksum 66 | public UInt16 GetChecksum() 67 | { 68 | UInt32 chksm = 0; 69 | byte[] data = GetBytes(); 70 | int packetSize = MessageSize + kIcmpHeaderSize; 71 | int index = 0; 72 | 73 | while (index < packetSize) 74 | { 75 | if (index + 2 > packetSize) 76 | { 77 | chksm += data[index]; 78 | } 79 | else 80 | { 81 | chksm += (uint)BitConverter.ToUInt16(data, index); 82 | } 83 | index += 2; 84 | } 85 | 86 | chksm = (chksm >> 16) + (chksm & 0xffff); 87 | chksm += (chksm >> 16); 88 | 89 | return (UInt16)(~chksm); 90 | } 91 | 92 | public string PrettyPrint() 93 | { 94 | return $"Type={Type.ToString()} Code={Code.ToString()} Checksum={Checksum} MessageSize={MessageSize}"; 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /src/Network/IPv4.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace PowerPing 4 | { 5 | /// 6 | /// IPv4 class, for creating Internet Protocol Version 4 (IPv4) packet objects 7 | /// 8 | internal class IPv4 9 | { 10 | // Packet header 11 | public byte Version; // Only 4 bits used (cos byte is _8_ bits) 12 | public byte HeaderLength; // Only 4 bits used 13 | public byte TypeOfService; // More explict? 14 | public UInt16 TotalLength; 15 | public UInt16 Identification; 16 | public byte Flags; // Only 3 bits used 17 | public UInt16 FragmentOffset; 18 | public byte TimeToLive; 19 | public byte Protocol; 20 | public UInt16 HeaderChecksum; 21 | public IPAddress Source; 22 | public IPAddress Destination; 23 | 24 | // Packet data 25 | public byte[] Data; 26 | 27 | // Constructors 28 | public IPv4() 29 | { 30 | Source = IPAddress.Any; 31 | Destination = IPAddress.Any; 32 | Data = new byte[4]; 33 | } 34 | 35 | public IPv4(byte[] data, int size) 36 | { 37 | //https://stackoverflow.com/a/10493604 38 | //https://en.wikipedia.org/wiki/IPv4#Header 39 | Version = (byte)((data[0] >> 4) & 0xF); // shift version to first 4 bits and then use a mask to only copy the first 4 bits using AND (mask is just 4 1 bits so with AND only the first bytes will pass the and operation with 00001111 (masking may not be needed here, shift might be enough 40 | HeaderLength = (byte)((data[0] & 0xF) * 0x4); // mask version bytes and times by 4 (length is number of 32 bit words (4 bytes) = 5 x 4) 41 | TypeOfService = data[1]; 42 | TotalLength = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 2)); 43 | Identification = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 4)); 44 | Flags = (byte)(data[6] >> 5); 45 | FragmentOffset = (UInt16)((data[6] & 0x1f) << 8 | data[7]); 46 | TimeToLive = data[8]; 47 | Protocol = data[9]; 48 | HeaderChecksum = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 10)); 49 | Source = new IPAddress(BitConverter.ToUInt32(data, 12)); 50 | Destination = new IPAddress(BitConverter.ToUInt32(data, 16)); 51 | 52 | Data = new byte[TotalLength - HeaderLength]; 53 | Buffer.BlockCopy(data, HeaderLength, Data, 0, TotalLength - HeaderLength); 54 | 55 | // body 56 | // 20 - size? 57 | } 58 | 59 | // Game plan: 60 | // - (DONE) Sort network order/big endian shit 61 | // - Implement Get bytes (remember hosttonetwork order) 62 | // - Read out ippacket thenreconvert it to bytes, check with wireshark to check result is right 63 | // - Add checks for options field, protocol field and if data size > 65k 64 | // - Add body 65 | // - Add checksum 66 | // - try sending 67 | 68 | // - Log ip and icmp packet (preset log levels like doom) 69 | 70 | public byte[] GetBytes() 71 | { 72 | int dataLength = Data != null ? Data.Length : 0; 73 | 74 | byte[] payload = new byte[HeaderLength + dataLength]; 75 | Buffer.BlockCopy(BitConverter.GetBytes((byte)(Version << 4) | HeaderLength), 0, payload, 0, 1); // TODO: divide length by 4, Shift 4 bits to right, OR (basically copy) the other 4 bits into where the version was 76 | Buffer.BlockCopy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(TypeOfService)), 0, payload, 1, 1); 77 | Buffer.BlockCopy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(TotalLength)), 0, payload, 2, 2); 78 | Buffer.BlockCopy(BitConverter.GetBytes(Identification), 0, payload, 4, 2); 79 | byte[] test = BitConverter.GetBytes(FragmentOffset); 80 | //Buffer.BlockCopy(BitConverter.GetBytes((byte)(Flags << 5) | test[0]), 0, payload, ); 81 | 82 | return payload; 83 | } 84 | 85 | public UInt16 GetChecksum() 86 | { 87 | return 0; 88 | } 89 | 90 | public string PrettyPrint() 91 | { 92 | return $"version={Version.ToString()} HeaderLength={HeaderLength.ToString()} TypeOfService={TypeOfService.ToString()}" + 93 | $" TotalLength={TotalLength} Identification={Identification} flags={Flags.ToString()} FragmentationOffset={FragmentOffset}" + 94 | $" TimeToLive={TimeToLive} Protocol={Protocol} HeaderCheckum={HeaderChecksum} ({HeaderChecksum.ToString("X")}) SourceAddress={Source.ToString()} DestinationAddress={Destination.ToString()}"; 95 | 96 | // TODO: print raw bytes 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/Network/Ping.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | using System.Diagnostics; 8 | using System.Net; 9 | using System.Net.Sockets; 10 | using System.Text; 11 | 12 | namespace PowerPing 13 | { 14 | /// 15 | /// Ping Class, used for constructing and sending ICMP data over a network. 16 | /// 17 | internal class Ping 18 | { 19 | public Action? OnRequest = null; 20 | public Action? OnReply = null; 21 | public Action? OnResultsUpdate = null; 22 | public Action? OnTimeout = null; 23 | public Action? OnStart = null; 24 | public Action? OnFinish = null; 25 | public Action? OnError = null; 26 | 27 | private PingAttributes _attributes = new(); 28 | private PingResults _results = new(); 29 | 30 | private Socket? _socket = null; 31 | private ICMP? _packet = null; 32 | private int _packetSize = 0; 33 | private IPEndPoint? _remoteEndpoint = null; 34 | private readonly CancellationToken _cancellationToken; 35 | 36 | private bool _debugIpHeader = false; 37 | private bool _debugTimings = false; 38 | 39 | private PingRequest _requestMessage = new(); 40 | private PingReply _responseMessage = new(); 41 | private PingTimeout _timeoutMessage = new(); 42 | private PingError _errorMesssage = new(); 43 | 44 | private ushort _currentSequenceNumber = 0; 45 | private int _currentReceiveTimeout = 0; 46 | 47 | private static readonly ushort _sessionId = Helper.GenerateSessionId(); 48 | 49 | public Ping( 50 | PingAttributes attributes, 51 | CancellationToken cancellationToken) 52 | { 53 | _attributes = attributes; 54 | _cancellationToken = cancellationToken; 55 | 56 | Setup(); 57 | } 58 | 59 | ~Ping() 60 | { 61 | Cleanup(); 62 | } 63 | 64 | public PingResults Send(PingAttributes attributes) 65 | { 66 | if (attributes != _attributes) 67 | { 68 | // Replace attributes if they are different 69 | _attributes = attributes; 70 | 71 | // Setup everything again 72 | Setup(); 73 | } 74 | 75 | return Send(); 76 | } 77 | 78 | public PingResults Send(string address) 79 | { 80 | _attributes.InputtedAddress = address; 81 | _attributes.ResolvedAddress = ""; 82 | 83 | // If we get a new address not only do we have to force a lookup 84 | // Do this by leaving ResolvedAddress blank, we also need to recreate the 85 | // remove endpoint that is based on that returned address 86 | ResolveAddress(); 87 | CreateRemoteEndpoint(); 88 | 89 | return Send(); 90 | } 91 | 92 | public PingResults Send() 93 | { 94 | // Reset some properties so they are ready for pinging 95 | Reset(); 96 | 97 | // Perform ping operation 98 | SendPacket(); 99 | 100 | return _results; 101 | } 102 | 103 | private void Setup() 104 | { 105 | CreateRawSocket(); 106 | SetupSocketOptions(); 107 | ResolveAddress(); 108 | CreateRemoteEndpoint(); 109 | ConstructPacket(); 110 | UpdatePacketChecksum(); 111 | } 112 | 113 | private void Cleanup() 114 | { 115 | // On deconstruction 116 | _socket?.Close(); 117 | _packet = null; 118 | _attributes.ResetToDefaultValues(); 119 | _results.Reset(); 120 | OnResultsUpdate = null; 121 | } 122 | 123 | private void Reset() 124 | { 125 | _currentSequenceNumber = 0; 126 | _currentReceiveTimeout = 0; 127 | 128 | // Wipe any previous results 129 | _results = new PingResults(); 130 | } 131 | 132 | private void CreateRawSocket() 133 | { 134 | // Determine what address family we are using 135 | AddressFamily family; 136 | ProtocolType protocol; 137 | if (_attributes.UseICMPv4) 138 | { 139 | family = AddressFamily.InterNetwork; 140 | protocol = ProtocolType.Icmp; 141 | } 142 | else 143 | { 144 | family = AddressFamily.InterNetworkV6; 145 | protocol = ProtocolType.IcmpV6; 146 | } 147 | 148 | // Create the raw socket 149 | try 150 | { 151 | _socket = new Socket(family, SocketType.Raw, protocol); 152 | } 153 | catch (SocketException e) 154 | { 155 | if (OnError != null) 156 | { 157 | _errorMesssage.Message = 158 | "PowerPing uses raw sockets which require Administrative rights to create." + Environment.NewLine + 159 | "(You can find more info at https://github.com/Killeroo/PowerPing/issues/110)" + Environment.NewLine + 160 | "Make sure you are running as an Administrator and try again."; 161 | _errorMesssage.Exception = e; 162 | _errorMesssage.Timestamp = DateTime.Now; 163 | _errorMesssage.Fatal = true; 164 | 165 | OnError.Invoke(_errorMesssage); 166 | 167 | // Exit on fatal error 168 | Helper.ExitWithError(); 169 | } 170 | 171 | } 172 | } 173 | 174 | private void SetupSocketOptions() 175 | { 176 | if (_socket == null) 177 | { 178 | return; 179 | } 180 | 181 | _socket.Ttl = (short)_attributes.Ttl; 182 | _socket.DontFragment = _attributes.DontFragment; 183 | _socket.ReceiveBufferSize = _attributes.ReceiveBufferSize; 184 | } 185 | 186 | private void ResolveAddress() 187 | { 188 | // If we have not been given a resolved address, perform dns query for inputted address 189 | if (_attributes.ResolvedAddress == "") 190 | { 191 | AddressFamily family = _attributes.UseICMPv4 ? AddressFamily.InterNetwork : AddressFamily.InterNetworkV6; 192 | _attributes.ResolvedAddress = Lookup.QueryDNS(_attributes.InputtedAddress, family); 193 | } 194 | } 195 | 196 | private void CreateRemoteEndpoint() 197 | { 198 | // Convert our resolved address 199 | IPAddress addr = IPAddress.Parse(_attributes.ResolvedAddress); 200 | 201 | // Create the endpoint we are going to recieve from 202 | _remoteEndpoint = new IPEndPoint(addr, 0); 203 | } 204 | 205 | private void SetSocketReceiveTimeout(int timeout) 206 | { 207 | if (_socket == null) 208 | { 209 | return; 210 | } 211 | 212 | if (timeout != _currentReceiveTimeout) 213 | { 214 | _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, timeout); 215 | _currentReceiveTimeout = timeout; 216 | } 217 | } 218 | 219 | private void ConstructPacket() 220 | { 221 | _packet = new ICMP(); 222 | _packet.Type = _attributes.Type; 223 | _packet.Code = _attributes.Code; 224 | 225 | // Work out what our intial payload will be and add to packet 226 | byte[] payload; 227 | if (_attributes.ArtificalMessageSize != -1) 228 | { 229 | payload = Helper.GenerateByteArray(_attributes.ArtificalMessageSize); 230 | } 231 | else 232 | { 233 | payload = Encoding.ASCII.GetBytes(_attributes.Message); 234 | } 235 | UpdatePacketMessage(payload); 236 | 237 | // Add identifier to ICMP message 238 | Buffer.BlockCopy(BitConverter.GetBytes(_sessionId), 0, _packet.Message, 0, 2); 239 | } 240 | 241 | private void UpdatePacketSequenceNumber(int sequence) 242 | { 243 | if (_packet == null) 244 | { 245 | return; 246 | } 247 | 248 | _currentSequenceNumber = (ushort)sequence; 249 | Buffer.BlockCopy(BitConverter.GetBytes(_currentSequenceNumber), 0, _packet.Message, 2, 2); 250 | } 251 | 252 | private void UpdatePacketMessage(byte[] message) 253 | { 254 | const int kIcmpIdAndSequenceSize = 4; 255 | 256 | if (_packet == null) 257 | { 258 | return; 259 | } 260 | 261 | // Increase size of packet message if we need to 262 | if (message.Length + kIcmpIdAndSequenceSize > _packet.Message.Length) 263 | { 264 | byte[] newPacketData = new byte[message.Length + kIcmpIdAndSequenceSize]; 265 | 266 | // Copy over id and sequence number 267 | Buffer.BlockCopy(_packet.Message, 0, newPacketData, 0, kIcmpIdAndSequenceSize); 268 | _packet.Message = newPacketData; 269 | } 270 | 271 | // Copy into packet 272 | Buffer.BlockCopy(message, 0, _packet.Message, kIcmpIdAndSequenceSize, message.Length); 273 | 274 | // Update message size 275 | if (message.Length + kIcmpIdAndSequenceSize != _packetSize) 276 | { 277 | _packet.MessageSize = message.Length + kIcmpIdAndSequenceSize; 278 | _packetSize = _packet.MessageSize + kIcmpIdAndSequenceSize; 279 | } 280 | } 281 | 282 | private void UpdatePacketChecksum() 283 | { 284 | if (_packet == null) 285 | { 286 | return; 287 | } 288 | 289 | _packet.Checksum = 0; 290 | UInt16 chksm = _packet.GetChecksum(); 291 | _packet.Checksum = chksm; 292 | } 293 | 294 | private void SendPacket() 295 | { 296 | if (_remoteEndpoint == null || _socket == null || _packet == null) 297 | { 298 | return; 299 | } 300 | 301 | byte[] receiveBuffer = new byte[20 /* Ipv4 header length */ + 4 /* ICMP header length */ + _packet.MessageSize + _attributes.ReceiveBufferSize]; 302 | int bytesRead = 0; 303 | 304 | OnStart?.Invoke(_attributes); 305 | _results.Start(); 306 | 307 | // Sending loop 308 | for (int index = 1; _attributes.Continous || index <= _attributes.Count; index++) 309 | { 310 | if (index != 1) 311 | { 312 | // Wait for set interval before sending again or cancel if requested 313 | if (_cancellationToken.WaitHandle.WaitOne(_attributes.Interval)) 314 | { 315 | break; 316 | } 317 | 318 | // Generate random interval when RandomTiming flag is set 319 | if (_attributes.RandomTiming) 320 | { 321 | _attributes.Interval = Helper.RandomInt(5000, 100000); 322 | } 323 | } 324 | 325 | // Update packet before sending 326 | UpdatePacketSequenceNumber(index); 327 | if (_attributes.RandomMessage) 328 | { 329 | UpdatePacketMessage(Encoding.ASCII.GetBytes(Helper.RandomString())); 330 | } 331 | UpdatePacketChecksum(); 332 | 333 | try 334 | { 335 | // If there were extra responses from a prior request, ignore them 336 | while (_socket.Available != 0) 337 | { 338 | bytesRead = _socket.Receive(receiveBuffer); 339 | } 340 | 341 | // Send ping request 342 | _socket.SendTo(_packet.GetBytes(), _packetSize, SocketFlags.None, _remoteEndpoint); // Packet size = message field + 4 header bytes 343 | long requestTimestamp = Stopwatch.GetTimestamp(); 344 | _results.IncrementSentPackets(); 345 | 346 | // Raise message on request sent 347 | if (OnRequest != null) 348 | { 349 | _requestMessage.Timestamp = DateTime.Now; 350 | _requestMessage.SequenceNumber = index; 351 | _requestMessage.Packet = _packet; 352 | _requestMessage.Destination = _remoteEndpoint; 353 | _requestMessage.PacketSize = _packetSize; 354 | 355 | OnRequest.Invoke(_requestMessage); 356 | } 357 | 358 | // Just for artifically testing higher ping response times 359 | if (_debugTimings) 360 | { 361 | Random rnd = new Random(); 362 | Thread.Sleep(rnd.Next(10, 400)); 363 | if (rnd.Next(3) == 1) { throw new SocketException(); } 364 | } 365 | 366 | // Try and recieve a packet 367 | ICMP response = ICMP.EmptyPacket; 368 | EndPoint responseEP = _remoteEndpoint; 369 | TimeSpan replyTime = TimeSpan.Zero; 370 | ReceivePacket(ref response, ref responseEP, ref replyTime, ref bytesRead, requestTimestamp); 371 | 372 | // Store response info 373 | _results.IncrementReceivedPackets(); 374 | _results.CountPacketType(response.Type); 375 | _results.SaveResponseTime(replyTime.TotalMilliseconds); 376 | } 377 | catch (IOException e) 378 | { 379 | if (OnError != null) 380 | { 381 | _errorMesssage.Message = "General transmit error"; 382 | _errorMesssage.Exception = e; 383 | _errorMesssage.Timestamp = DateTime.Now; 384 | _errorMesssage.Fatal = false; 385 | 386 | OnError.Invoke(_errorMesssage); 387 | } 388 | 389 | _results.SaveResponseTime(-1); 390 | _results.IncrementLostPackets(); 391 | } 392 | catch (SocketException) 393 | { 394 | if (OnTimeout != null) 395 | { 396 | _timeoutMessage.Timestamp = DateTime.Now; 397 | _timeoutMessage.SequenceNumber = index; 398 | _timeoutMessage.Endpoint = _remoteEndpoint; 399 | 400 | OnTimeout.Invoke(_timeoutMessage); 401 | } 402 | 403 | _results.SaveResponseTime(-1); 404 | _results.IncrementLostPackets(); 405 | } 406 | catch (OperationCanceledException) 407 | { 408 | _results.ScanWasCancelled = true; 409 | break; 410 | } 411 | catch (Exception e) 412 | { 413 | if (OnError != null) 414 | { 415 | _errorMesssage.Message = "General error occured"; 416 | _errorMesssage.Exception = e; 417 | _errorMesssage.Timestamp = DateTime.Now; 418 | _errorMesssage.Fatal = false; 419 | 420 | OnError.Invoke(_errorMesssage); 421 | } 422 | 423 | _results.SaveResponseTime(-1); 424 | _results.IncrementLostPackets(); 425 | } 426 | 427 | // Run callback (if provided) to notify of updated results 428 | OnResultsUpdate?.Invoke(_results); 429 | } 430 | 431 | _results.Stop(); 432 | OnFinish?.Invoke(_results); 433 | } 434 | 435 | private void ReceivePacket(ref ICMP response, ref EndPoint responseEndPoint, ref TimeSpan replyTime, ref int bytesRead, long requestTimestamp) 436 | { 437 | if (_socket == null || _packet == null) 438 | { 439 | return; 440 | } 441 | 442 | byte[] receiveBuffer = new byte[_attributes.ReceiveBufferSize]; 443 | int ttl = 0; 444 | 445 | // Wait for request 446 | do 447 | { 448 | // Cancel if requested 449 | _cancellationToken.ThrowIfCancellationRequested(); 450 | 451 | // Set receive timeout, limited to 250ms so we don't block very long without checking for 452 | // cancellation. If the requested ping timeout is longer, we will wait some more in subsequent 453 | // loop iterations until the requested ping timeout is reached. 454 | int remainingTimeout = (int)Math.Ceiling(_attributes.Timeout - replyTime.TotalMilliseconds); 455 | if (remainingTimeout <= 0) 456 | { 457 | throw new SocketException(); 458 | } 459 | SetSocketReceiveTimeout(Math.Min(remainingTimeout, 250)); 460 | 461 | // Wait for response 462 | try 463 | { 464 | bytesRead = _socket.ReceiveFrom(receiveBuffer, ref responseEndPoint); 465 | } 466 | catch (SocketException) 467 | { 468 | bytesRead = 0; 469 | } 470 | replyTime = new TimeSpan(Helper.StopwatchToTimeSpanTicks(Stopwatch.GetTimestamp() - requestTimestamp)); 471 | 472 | if (bytesRead != 0) 473 | { 474 | // Parse outter IPv4 header 475 | IPv4 header = new IPv4(receiveBuffer, bytesRead); 476 | ttl = header.TimeToLive; 477 | 478 | // Store reply packet 479 | response = new ICMP(receiveBuffer, bytesRead, header.HeaderLength); 480 | 481 | if (_debugIpHeader) 482 | { 483 | // Print out parsed IPv4 header data 484 | IPv4 ipheader = new(receiveBuffer, bytesRead); 485 | if (ipheader.Data != null) 486 | { 487 | ICMP ping = new(ipheader.Data, ipheader.TotalLength - ipheader.HeaderLength, 0); 488 | 489 | ipheader.GetBytes(); 490 | Console.BackgroundColor = ConsoleColor.Red; 491 | Console.ForegroundColor = ConsoleColor.Black; 492 | Console.WriteLine(header.PrettyPrint()); 493 | Console.ResetColor(); 494 | 495 | Console.BackgroundColor = ConsoleColor.Yellow; 496 | Console.ForegroundColor = ConsoleColor.Black; 497 | Console.WriteLine(ping.PrettyPrint()); 498 | Console.ResetColor(); 499 | } 500 | } 501 | 502 | // If we sent an echo and receive a response with a different identifier or sequence 503 | // number, ignore it (it could correspond to an older request that timed out) 504 | if (_packet.Type == 8 && response.Type == 0) 505 | { 506 | ushort responseSessionId = BitConverter.ToUInt16(response.Message, 0); 507 | ushort responseSequenceNum = BitConverter.ToUInt16(response.Message, 2); 508 | if (responseSessionId != _sessionId || responseSequenceNum != _currentSequenceNumber) 509 | { 510 | response = ICMP.EmptyPacket; 511 | } 512 | } 513 | } 514 | } while (response == ICMP.EmptyPacket); 515 | 516 | // Raise message on response 517 | if (OnReply != null) 518 | { 519 | _responseMessage.Packet = response; 520 | _responseMessage.EndpointAddress = responseEndPoint.ToString() ?? string.Empty; 521 | _responseMessage.Timestamp = DateTime.Now; 522 | _responseMessage.SequenceNumber = _currentSequenceNumber; 523 | _responseMessage.BytesRead = bytesRead; 524 | _responseMessage.RoundTripTime = replyTime; 525 | _responseMessage.TimeToLive = ttl; 526 | 527 | OnReply.Invoke(_responseMessage); 528 | } 529 | } 530 | } 531 | } -------------------------------------------------------------------------------- /src/PowerPing.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | true 7 | 8 | true 9 | enable 10 | enable 11 | 12 | 13 | 14 | false 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Program.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | namespace PowerPing 8 | { 9 | internal static class Program 10 | { 11 | private static readonly CancellationTokenSource _cancellationTokenSource = new (); 12 | private static DisplayConfiguration _displayConfiguration = new (); 13 | private static LogMessageHandler? _logMessageHandler = null; 14 | private static ConsoleMessageHandler? _consoleMessageHandler = null; 15 | 16 | public const bool BETA = false; 17 | 18 | /// 19 | /// Main entry point of PowerPing 20 | /// Parses arguments and runs operations 21 | /// 22 | /// Program arguments 23 | private static void Main(string[] args) 24 | { 25 | PingAttributes parsedAttributes = new PingAttributes(); 26 | 27 | // Show current version info 28 | //Display.Version(); 29 | 30 | // Check if no arguments 31 | if (args.Length == 0) 32 | { 33 | ConsoleDisplay.Help(); 34 | return; 35 | } 36 | 37 | // Parse command line arguments 38 | if (!CommandLine.Parse(args, ref parsedAttributes, ref _displayConfiguration)) 39 | { 40 | Helper.ErrorAndExit("Problem parsing arguments, use \"PowerPing /help\" or \"PowerPing /?\" for help."); 41 | } 42 | 43 | Helper.RequireInput = _displayConfiguration.RequireInput; 44 | 45 | // Find address/host in arguments 46 | if (parsedAttributes.Operation != PingOperation.Whoami && 47 | parsedAttributes.Operation != PingOperation.Listen) 48 | { 49 | if (!CommandLine.FindAddress(args, ref parsedAttributes)) 50 | { 51 | Helper.ErrorAndExit("Could not find correctly formatted address, please check and try again"); 52 | } 53 | } 54 | 55 | // Perform DNS lookup on inputted address 56 | // inputtedAttributes.ResolvedAddress = Lookup.QueryDNS(inputtedAttributes.InputtedAddress, inputtedAttributes.UseICMPv4 ? AddressFamily.InterNetwork : AddressFamily.InterNetworkV6); 57 | 58 | // Add Control C event handler 59 | if (parsedAttributes.Operation != PingOperation.Whoami && 60 | parsedAttributes.Operation != PingOperation.Location && 61 | parsedAttributes.Operation != PingOperation.Whois) 62 | { 63 | Console.CancelKeyPress += new ConsoleCancelEventHandler(ExitHandler); 64 | } 65 | 66 | // Set configuration 67 | ConsoleDisplay.Configuration = _displayConfiguration; 68 | ConsoleDisplay.CancellationToken = _cancellationTokenSource.Token; 69 | 70 | 71 | // Select correct function using opMode 72 | switch (parsedAttributes.Operation) 73 | { 74 | #pragma warning disable CS8604 // InputtedAddress has to be equal to something so disable null reference warning for this. 75 | case PingOperation.Listen: RunListenOperation(args, parsedAttributes); break; 76 | case PingOperation.Location: RunLocationOperation(parsedAttributes.InputtedAddress); break; 77 | case PingOperation.Whoami: RunWhoAmIOperation(); break; 78 | case PingOperation.Whois: RunWhoisOperation(parsedAttributes.InputtedAddress); break; 79 | case PingOperation.Graph: RunGraphOperation(parsedAttributes.InputtedAddress, _cancellationTokenSource.Token); break; 80 | case PingOperation.Flood: RunFloodOperation(parsedAttributes.InputtedAddress, _cancellationTokenSource.Token); break; 81 | case PingOperation.Scan: RunScanOperation(parsedAttributes, _cancellationTokenSource.Token); break; 82 | case PingOperation.Normal: RunNormalPingOperation(parsedAttributes, _cancellationTokenSource.Token); break; 83 | #pragma warning restore CS8604 // Possible null reference argument. 84 | 85 | default: 86 | Helper.ErrorAndExit("Could not determine ping operation"); 87 | break; 88 | } 89 | 90 | // Reset console colour 91 | ConsoleDisplay.ResetColor(); 92 | try { Console.CursorVisible = true; } catch (Exception) { } 93 | } 94 | 95 | /// 96 | /// Runs when Exit or Cancel event fires (normally when Ctrl-C) 97 | /// is pressed. Used to clean up and stop operations when exiting 98 | /// 99 | /// 100 | /// 101 | private static void ExitHandler(object? sender, ConsoleCancelEventArgs args) 102 | { 103 | // Cancel termination 104 | args.Cancel = true; 105 | 106 | // Request currently running job to finish up 107 | _cancellationTokenSource.Cancel(); 108 | 109 | // Reset colour on exit 110 | Console.ResetColor(); 111 | } 112 | 113 | private static void RunNormalPingOperation( 114 | PingAttributes attributes, 115 | CancellationToken cancellationToken) 116 | { 117 | if (attributes == null) 118 | { 119 | return; 120 | } 121 | 122 | Ping p = new Ping(attributes, cancellationToken); 123 | 124 | if (attributes.EnableFileLogging) 125 | { 126 | // Setup the path we are going to save the log to 127 | // (generate the name if needed) 128 | attributes.LogFilePath = LogFile.SetupPath(attributes.LogFilePath, attributes.InputtedAddress); 129 | _logMessageHandler = new LogMessageHandler(attributes.LogFilePath, _displayConfiguration); 130 | 131 | // Add callbacks for logging to a file 132 | // These need to be first.. so they get run first 133 | p.OnStart += _logMessageHandler.OnStart; 134 | p.OnFinish += _logMessageHandler.OnFinish; 135 | p.OnTimeout += _logMessageHandler.OnTimeout; 136 | p.OnRequest += _logMessageHandler.OnRequest; 137 | p.OnReply += _logMessageHandler.OnReply; 138 | p.OnError += _logMessageHandler.OnError; 139 | } 140 | 141 | // Add handler to display ping events 142 | _consoleMessageHandler = new ConsoleMessageHandler(_displayConfiguration, _cancellationTokenSource.Token); 143 | 144 | // Add callbacks for console display 145 | p.OnStart += _consoleMessageHandler.OnStart; 146 | p.OnFinish += _consoleMessageHandler.OnFinish; 147 | p.OnTimeout += _consoleMessageHandler.OnTimeout; 148 | p.OnRequest += _consoleMessageHandler.OnRequest; 149 | p.OnReply += _consoleMessageHandler.OnReply; 150 | p.OnError += _consoleMessageHandler.OnError; 151 | 152 | // Send the pings! 153 | p.Send(); 154 | } 155 | 156 | private static void RunFloodOperation(string address, CancellationToken cancellationToken) 157 | { 158 | Flood f = new Flood(); 159 | 160 | f.Start(address, cancellationToken); 161 | } 162 | 163 | private static void RunGraphOperation(string address, CancellationToken cancellationToken) 164 | { 165 | Graph g = new Graph(address, cancellationToken); 166 | 167 | g.Start(); 168 | } 169 | 170 | private static void RunWhoisOperation(string address) 171 | { 172 | Lookup.QueryWhoIs(address); 173 | 174 | if (_displayConfiguration.RequireInput) 175 | { 176 | Helper.WaitForUserInput(); 177 | } 178 | } 179 | 180 | private static void RunWhoAmIOperation() 181 | { 182 | Console.WriteLine(Lookup.GetAddressLocationInfo("", true)); 183 | if (_displayConfiguration.RequireInput) 184 | { 185 | Helper.WaitForUserInput(); 186 | } 187 | } 188 | 189 | private static void RunListenOperation(string[] args, PingAttributes attributes) 190 | { 191 | // If we find an address then pass it to listen, otherwise start it without one 192 | if (CommandLine.FindAddress(args, ref attributes)) 193 | { 194 | Listen.Start(_cancellationTokenSource.Token, attributes.InputtedAddress); 195 | } 196 | else 197 | { 198 | Listen.Start(_cancellationTokenSource.Token); 199 | } 200 | } 201 | 202 | private static void RunLocationOperation(string address) 203 | { 204 | Console.WriteLine(Lookup.GetAddressLocationInfo(address, true)); 205 | if (_displayConfiguration.RequireInput) 206 | { 207 | Helper.WaitForUserInput(); 208 | } 209 | } 210 | 211 | private static void RunScanOperation(PingAttributes attributes, CancellationToken cancellationToken) 212 | { 213 | if (attributes.EnableFileLogging) 214 | { 215 | attributes.LogFilePath = LogFile.SetupPath(attributes.LogFilePath, attributes.InputtedAddress); 216 | _logMessageHandler = new LogMessageHandler(attributes.LogFilePath, _displayConfiguration); 217 | 218 | Scan.OnScanFinished += _logMessageHandler.OnScanFinished; 219 | } 220 | 221 | // Add callbacks for console display 222 | _consoleMessageHandler = new ConsoleMessageHandler(_displayConfiguration, _cancellationTokenSource.Token); 223 | Scan.OnScanProgress += _consoleMessageHandler.OnScanProgress; 224 | Scan.OnScanFinished += _consoleMessageHandler.OnScanFinished; 225 | 226 | Scan.Start(attributes.InputtedAddress, cancellationToken); 227 | } 228 | } 229 | } -------------------------------------------------------------------------------- /src/Properties/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | True 15 | 16 | 17 | False 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PowerPing")] 9 | [assembly: AssemblyDescription("Advanced ICMP Ping Utility")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Matthew Carney")] 12 | [assembly: AssemblyProduct("PowerPing")] 13 | [assembly: AssemblyCopyright("Copyright © Matthew Carney 2024")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | [assembly: ComVisible(false)] 18 | [assembly: Guid("4fa774f6-c5f6-4429-bf60-c3cfc61ec560")] 19 | 20 | [assembly: AssemblyVersion("1.3.5")] -------------------------------------------------------------------------------- /src/Properties/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 59 | 60 | 61 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/Structures/PingAttributes.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | namespace PowerPing 8 | { 9 | public enum PingOperation 10 | { 11 | Normal, 12 | Flood, 13 | Listen, 14 | Scan, 15 | Graph, 16 | Location, 17 | Whoami, 18 | Whois 19 | } 20 | 21 | /// 22 | /// Stores attributes of a ping operation 23 | /// 24 | public class PingAttributes 25 | { 26 | // Properties 27 | public string InputtedAddress { get; set; } // Address as it was inputted by user 28 | public string ResolvedAddress { get; set; } // Resolved address (after lookup) the actual address ping is sent to 29 | public string SourceAddress { get; set; } // Source address of IP packet (not implemented yet) 30 | public string Message { get; set; } // Message to store in ICMP message field 31 | public int Interval { get; set; } // Time interval between each ping 32 | public int Timeout { get; set; } // Time (in milliseconds) before timeout occurs 33 | public int Count { get; set; } // Number of pings to send 34 | public int Ttl { get; set; } // IP packet Time to live (TTL) value (https://en.wikipedia.org/wiki/Time_to_live) 35 | public int ReceiveBufferSize { get; set; } // Size of buffer used when receiving pings 36 | public int ArtificalMessageSize { get; set; } // Sets the ICMP message to be a specifed number of empty bytes 37 | public byte Type { get; set; } // ICMP type field value (https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol#Control_messages) 38 | public byte Code { get; set; } // ICMP code field value (https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol#Control_messages) 39 | 40 | // Options 41 | public int BeepMode { get; set; } // Beep level - 1 to beep on timeout, 2 for beep on reply, 0 for no beep 42 | public bool Continous { get; set; } // Option to continously send pings 43 | public bool UseICMPv4 { get; set; } // Force use of IPv4/ICMPv4 44 | public bool UseICMPv6 { get; set; } // Force use of IPv6/ICMPv6 (not implemented yet) 45 | public bool RandomMessage { get; set; } // Fills ICMP message field with random characters 46 | public bool DontFragment { get; set; } // Sets the IP head Don't Fragment flag 47 | public bool RandomTiming { get; set; } // Use random send interval between pings 48 | public bool EnableFileLogging { get; set; } // Log results to a file 49 | public string LogFilePath { get; set; } // File to log file to 50 | 51 | public PingOperation Operation { get; set; } // Current ping operation being performed 52 | 53 | public PingAttributes() 54 | { 55 | // Analyser complains about these not being null even though they are set in ResetToDefaultValues (sigh...) 56 | InputtedAddress = string.Empty; 57 | ResolvedAddress = string.Empty; 58 | SourceAddress = string.Empty; 59 | LogFilePath = string.Empty; 60 | Message = Helper.RandomString(26); 61 | 62 | ResetToDefaultValues(); 63 | } 64 | 65 | public PingAttributes(PingAttributes attributes) 66 | { 67 | InputtedAddress = attributes.InputtedAddress; 68 | ResolvedAddress = attributes.ResolvedAddress; 69 | SourceAddress = attributes.SourceAddress; 70 | Message = attributes.Message; 71 | LogFilePath = attributes.LogFilePath; 72 | Interval = attributes.Interval; 73 | Timeout = attributes.Timeout; 74 | Count = attributes.Count; 75 | Ttl = attributes.Ttl; 76 | Type = attributes.Type; 77 | Code = attributes.Code; 78 | ArtificalMessageSize = attributes.ArtificalMessageSize; 79 | BeepMode = attributes.BeepMode; 80 | ReceiveBufferSize = attributes.ReceiveBufferSize; 81 | 82 | Continous = attributes.Continous; 83 | UseICMPv4 = attributes.UseICMPv4; 84 | UseICMPv6 = attributes.UseICMPv6; 85 | RandomMessage = attributes.RandomMessage; 86 | DontFragment = attributes.DontFragment; 87 | RandomTiming = attributes.RandomTiming; 88 | Operation = attributes.Operation; 89 | } 90 | 91 | public void ResetToDefaultValues() 92 | { 93 | // Default properties 94 | InputtedAddress = string.Empty; 95 | ResolvedAddress = string.Empty; 96 | SourceAddress = string.Empty; 97 | LogFilePath = string.Empty; 98 | Message = Helper.RandomString(26); 99 | Interval = 1000; 100 | Timeout = 3000; 101 | Count = 5; 102 | Ttl = 255; 103 | Type = 0x08; 104 | Code = 0x00; 105 | ArtificalMessageSize = -1; 106 | BeepMode = 0; 107 | ReceiveBufferSize = 5096; 108 | 109 | // Default options 110 | Continous = false; 111 | UseICMPv4 = true; 112 | UseICMPv6 = false; 113 | RandomMessage = false; 114 | DontFragment = false; 115 | RandomTiming = false; 116 | Operation = PingOperation.Normal; 117 | LogFilePath = string.Empty; 118 | } 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /src/Structures/PingError.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | * ************************************************************************/ 6 | 7 | using System; 8 | 9 | namespace PowerPing 10 | { 11 | public struct PingError 12 | { 13 | public DateTime Timestamp; 14 | public string Message; 15 | public Exception Exception; 16 | public bool Fatal; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Structures/PingReply.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | * ************************************************************************/ 6 | 7 | using System; 8 | using System.Net; 9 | 10 | namespace PowerPing 11 | { 12 | public struct PingReply 13 | { 14 | public ICMP Packet; 15 | public string EndpointAddress; 16 | public DateTime Timestamp; 17 | public int SequenceNumber; 18 | public TimeSpan RoundTripTime; 19 | public int TimeToLive; 20 | public int BytesRead; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Structures/PingRequest.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | * ************************************************************************/ 6 | 7 | using System; 8 | using System.Net; 9 | 10 | namespace PowerPing 11 | { 12 | public struct PingRequest 13 | { 14 | public IPEndPoint Destination; 15 | public ICMP Packet; 16 | public DateTime Timestamp; 17 | public int SequenceNumber; 18 | public int PacketSize; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Structures/PingResults.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | * ************************************************************************/ 6 | 7 | using System.Diagnostics; 8 | 9 | namespace PowerPing 10 | { 11 | /// 12 | /// Stores the results of a ping operation 13 | /// 14 | public class PingResults 15 | { 16 | // Properties 17 | public DateTime StartTime { get; private set; } // Time operation started at 18 | public DateTime EndTime { get; private set; } // Time operation finished 19 | public TimeSpan TotalRunTime { get { return _operationTimer.Elapsed; } } // Total ping operation runtime 20 | public ulong Sent { get; private set; } // Number of sent ping packets 21 | public ulong Received { get; private set; } // Number of received packets 22 | public ulong Lost { get; private set; } // Amount of lost packets 23 | public double MaxTime { get; private set; } // Highest ping reply time 24 | public double MinTime { get; private set; } // Lowest ping reply time 25 | public double AvgTime { get; private set; } // Average reply time 26 | public double CurrTime { get; private set; } // Most recent packet response time 27 | public ulong ErrorPackets { get; private set; } // Number of Error packet received 28 | public ulong GoodPackets { get; private set; } // Number of good replies received 29 | public ulong OtherPackets { get; private set; } // Number of other packet types received 30 | public bool HasOverflowed { get; set; } // Specifies if any of the results have overflowed 31 | public bool ScanWasCancelled { get; set; } // Whether the scan was canceled early 32 | 33 | private readonly Stopwatch _operationTimer = new Stopwatch(); // Used to time total time spent doing operation 34 | private double _responseTimeSum = 0; // Sum of all reply times (used to work out general average 35 | 36 | public PingResults() => Reset(); 37 | 38 | public void Reset() 39 | { 40 | // Default properties 41 | StartTime = DateTime.MinValue; 42 | EndTime = DateTime.MinValue; 43 | 44 | Sent = 0; 45 | Received = 0; 46 | Lost = 0; 47 | MaxTime = 0; 48 | MinTime = 0; 49 | AvgTime = 0; 50 | CurrTime = -1; 51 | ErrorPackets = 0; 52 | GoodPackets = 0; 53 | OtherPackets = 0; 54 | 55 | HasOverflowed = false; 56 | ScanWasCancelled = false; 57 | } 58 | 59 | public void Start() 60 | { 61 | // Get local start time 62 | StartTime = DateTime.Now; 63 | 64 | // Start timing operation 65 | _operationTimer.Start(); 66 | } 67 | 68 | public void Stop() 69 | { 70 | EndTime = DateTime.Now; 71 | 72 | _operationTimer.Stop(); 73 | } 74 | 75 | public void SaveResponseTime(double time) 76 | { 77 | if (time == -1f) 78 | { 79 | CurrTime = 0; 80 | return; 81 | } 82 | 83 | // BUG: Converting from long to double might be causing precisson loss 84 | // Check response time against current max and min 85 | if (time > MaxTime) 86 | { 87 | MaxTime = time; 88 | } 89 | 90 | if (time < MinTime || MinTime == 0) 91 | { 92 | MinTime = time; 93 | } 94 | 95 | try 96 | { 97 | // Work out average 98 | _responseTimeSum = checked(_responseTimeSum + time); 99 | AvgTime = _responseTimeSum / Received; // Avg = Total / Count 100 | } 101 | catch (OverflowException) 102 | { 103 | HasOverflowed = true; 104 | } 105 | CurrTime = time; 106 | } 107 | 108 | public void CountPacketType(int type) 109 | { 110 | try 111 | { 112 | if (type == 0) 113 | { 114 | GoodPackets = checked(GoodPackets + 1); 115 | } 116 | else if (type == 3 || type == 4 || type == 5 || type == 11) 117 | { 118 | ErrorPackets = checked(ErrorPackets + 1); 119 | } 120 | else 121 | { 122 | OtherPackets = checked(OtherPackets + 1); 123 | } 124 | } 125 | catch (OverflowException) 126 | { 127 | HasOverflowed = true; 128 | } 129 | } 130 | 131 | public void IncrementSentPackets() 132 | { 133 | try 134 | { 135 | Sent = checked(Sent + 1); 136 | } 137 | catch (OverflowException) 138 | { 139 | HasOverflowed = true; 140 | } 141 | } 142 | 143 | public void IncrementReceivedPackets() 144 | { 145 | try 146 | { 147 | Received = checked(Received + 1); 148 | } 149 | catch (OverflowException) 150 | { 151 | HasOverflowed = true; 152 | } 153 | } 154 | 155 | public void IncrementLostPackets() 156 | { 157 | try 158 | { 159 | Lost = checked(Lost + 1); 160 | } 161 | catch (OverflowException) 162 | { 163 | HasOverflowed = true; 164 | } 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /src/Structures/PingTimeout.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | * ************************************************************************/ 6 | 7 | using System; 8 | using System.Net; 9 | 10 | namespace PowerPing 11 | { 12 | public struct PingTimeout 13 | { 14 | public IPEndPoint Endpoint; 15 | public DateTime Timestamp; 16 | public int SequenceNumber; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Utils/Exceptions.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | using System; 8 | 9 | namespace PowerPing 10 | { 11 | // Custom exceptions 12 | // (used in arguments parsing) 13 | public class ArgumentFormatException : Exception { } 14 | public class MissingArgumentException : Exception { } 15 | public class InvalidArgumentException : Exception { } 16 | } 17 | -------------------------------------------------------------------------------- /src/Utils/ExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | using System; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace PowerPing 12 | { 13 | static class ExtensionMethods 14 | { 15 | /// 16 | /// Waits for a task to complete and returns its value. Similar to calling task.Result which 17 | /// will also wait if it needs to, but this method allows for use of a cancellation token. 18 | /// 19 | /// The task to wait on. 20 | /// The cancellation token to signal if the wait should end early. 21 | /// The task's result. 22 | public static T WaitForResult(this Task task, CancellationToken cancellationToken) 23 | { 24 | try { 25 | task.Wait(cancellationToken); 26 | } catch (AggregateException ex) when (ex.InnerExceptions.Count == 1) { 27 | // Wait wraps everything in an AggregateException, so we'll unwrap it here to give the 28 | // caller the orignal exception. There should only be one InnerException since we only 29 | // waited on one task, but the validation is just to be safe. 30 | throw ex.InnerExceptions[0]; 31 | } 32 | return task.Result; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Utils/Helper.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | using System.Diagnostics; 8 | using System.Net; 9 | using System.Reflection; 10 | using System.Security.Cryptography; 11 | using System.Text.Json; 12 | using System.Text.RegularExpressions; 13 | 14 | namespace PowerPing 15 | { 16 | /// 17 | /// Class for miscellaneous methods 18 | /// 19 | public static class Helper 20 | { 21 | public static bool RequireInput = false; 22 | 23 | private static readonly string _ipv4Regex = @"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}"; 24 | private static readonly string _urlRegex = @"[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?"; 25 | private static readonly string _validScanRangeRegex = @"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|\-|$)){5}"; 26 | 27 | private static readonly double _stopwatchToTimeSpanTicksScale = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency; 28 | private static readonly double _timeSpanToStopwatchTicksScale = (double)Stopwatch.Frequency / TimeSpan.TicksPerSecond; 29 | 30 | /// 31 | /// Pause program and wait for user input 32 | /// 33 | public static void WaitForUserInput() 34 | { 35 | // Don't wait for output when the output of the program is being redirected 36 | // (to say a file or something) 37 | if (Console.IsOutputRedirected) 38 | return; 39 | 40 | Console.Write("Press any key to continue..."); 41 | Console.WriteLine(); 42 | 43 | // Work around if readkey isnt supported 44 | try { Console.ReadKey(); } 45 | catch (InvalidOperationException) { Console.Read(); } 46 | } 47 | 48 | /// 49 | /// Prints and error message and then exits with exit code 1 50 | /// 51 | /// Error message to print 52 | /// Wait for user input before exitingt 53 | public static void ErrorAndExit(string msg) 54 | { 55 | ConsoleDisplay.Error(msg); 56 | 57 | if (RequireInput) 58 | { 59 | Helper.WaitForUserInput(); 60 | } 61 | 62 | Environment.Exit(1); 63 | } 64 | 65 | /// 66 | /// Exits the application with an error code 67 | /// 68 | public static void ExitWithError() 69 | { 70 | Environment.Exit(1); 71 | } 72 | 73 | /// 74 | /// Check if long value is between a range 75 | /// 76 | /// 77 | /// Lower range 78 | /// Upper range 79 | /// 80 | public static bool IsBetween(long value, long left, long right) 81 | { 82 | return value > left && value < right; 83 | } 84 | 85 | /// 86 | /// Produces cryprographically secure string of specified length 87 | /// 88 | /// 89 | /// 90 | public static String RandomString(int len = 11) 91 | { 92 | RandomNumberGenerator.Create(); 93 | byte[] randomBytes = RandomNumberGenerator.GetBytes(len); 94 | string result = Convert.ToBase64String(randomBytes); 95 | 96 | // Remove '=' from end of string 97 | return result.Remove(result.Length - 1); 98 | } 99 | 100 | /// 101 | /// Produces cryprographically secure int of specified length 102 | /// 103 | /// 104 | /// 105 | public static int RandomInt(int min, int max) 106 | { 107 | return RandomNumberGenerator.GetInt32(min, max); 108 | } 109 | 110 | /// 111 | /// Generates a byte array of a given size, used for adding size 112 | /// to icmp packet 113 | /// 114 | /// 115 | /// 116 | public static byte[] GenerateByteArray(int size) 117 | { 118 | byte[] array = new byte[size]; 119 | for (int i = 0; i < size; i++) 120 | { 121 | array[i] = 0x00; 122 | } 123 | 124 | return array; 125 | } 126 | 127 | /// 128 | /// Checks if a string is a valid IPv4 address 129 | /// 130 | /// 131 | /// 132 | public static bool IsIPv4Address(string address) 133 | { 134 | return Regex.Match(address, _ipv4Regex).Success; 135 | } 136 | 137 | /// 138 | /// Checks if a string is a valid url 139 | /// 140 | /// 141 | /// 142 | public static bool IsURL(string url) 143 | { 144 | return Regex.Match(url, _urlRegex).Success; 145 | } 146 | 147 | /// 148 | /// Checks if a string is a valid range. 149 | /// Range looks like with normal IP address with dash to specify range to scan: 150 | /// EG 192.168.1.1-255 to scan every address between 192.168.1.1-192.168.1.255 151 | /// 152 | /// 153 | /// 154 | /// TODO: Allow for scan range to specified in any segment 155 | public static bool IsValidScanRange(string range) 156 | { 157 | return Regex.Match(range, _validScanRangeRegex).Success; 158 | } 159 | 160 | /// 161 | /// Runs a function inside a Task instead of on the current thread. This allows for use of a cancellation 162 | /// token to resume the current thread (by throwing OperationCanceledException) before the function finishes. 163 | /// 164 | /// 165 | /// 166 | /// 167 | public static T RunWithCancellationToken(Func func, CancellationToken cancellationToken) 168 | { 169 | return Task.Run(func, cancellationToken).WaitForResult(cancellationToken); 170 | } 171 | 172 | /// 173 | /// Generates session id to store in packet using underlying process id 174 | /// 175 | /// 176 | public static ushort GenerateSessionId() 177 | { 178 | uint n = (uint)Process.GetCurrentProcess().Id; 179 | return (ushort)(n ^ (n >> 16)); 180 | } 181 | 182 | /// 183 | /// Split list into x equally sized lists 184 | /// 185 | /// https://stackoverflow.com/a/3893011 186 | public static List[] PartitionList(List list, int partitionCount) 187 | { 188 | if (list == null) 189 | throw new ArgumentNullException(); 190 | 191 | if (partitionCount < 1) 192 | throw new ArgumentOutOfRangeException(); 193 | 194 | List[] partitions = new List[partitionCount]; 195 | 196 | int maxSize = (int)Math.Ceiling((double)(list.Count / (double)partitionCount)); 197 | int currentOffset = 0; 198 | 199 | for (int i = 0; i < partitions.Length; i++) 200 | { 201 | partitions[i] = new List(); 202 | for (int j = currentOffset; j < currentOffset + maxSize; j++) 203 | { 204 | if (j >= list.Count) 205 | break; 206 | 207 | partitions[i].Add(list[j]); 208 | } 209 | 210 | currentOffset += maxSize; 211 | } 212 | 213 | return partitions; 214 | } 215 | 216 | public static long StopwatchToTimeSpanTicks(long stopwatchTicks) 217 | { 218 | return (long)(stopwatchTicks * _stopwatchToTimeSpanTicksScale); 219 | } 220 | 221 | public static long TimeSpanToStopwatchTicks(long timeSpanTicks) 222 | { 223 | return (long)(timeSpanTicks * _timeSpanToStopwatchTicksScale); 224 | } 225 | 226 | /// 227 | /// Gets the version this assembly 228 | /// 229 | /// 230 | public static string GetVersionString() 231 | { 232 | string version = ""; 233 | Version? v = Assembly.GetExecutingAssembly().GetName().Version; 234 | 235 | if (v != null) 236 | { 237 | version = "v" + v.Major + "." + v.Minor + "." + v.Build + " (r" + v.Revision + ") " + (Program.BETA ? "[BETA]" : ""); 238 | } 239 | else 240 | { 241 | version = ""; 242 | } 243 | 244 | return version; 245 | } 246 | 247 | /// 248 | /// Checks if the file path already exists, if so then a (1) is added the end of the filename 249 | /// 250 | /// 251 | /// 252 | public static string CheckForDuplicateFile(string filepath) 253 | { 254 | string filename = Path.GetFileNameWithoutExtension(filepath); 255 | string extension = Path.GetExtension(filepath); 256 | 257 | string? outDir = Path.GetDirectoryName(filepath); 258 | string? path = string.IsNullOrEmpty(outDir) ? Path.GetPathRoot(filepath) : outDir; 259 | 260 | string currentFilePath = filepath; 261 | bool goodFilename = false; 262 | int counter = 0; 263 | 264 | // Loop through possible names till we find one that doesn't already exist 265 | while (goodFilename == false) 266 | { 267 | if (File.Exists(currentFilePath)) 268 | { 269 | counter++; 270 | currentFilePath = Path.Combine(path ?? string.Empty, $"{filename}({counter}){extension}"); 271 | } 272 | else 273 | { 274 | goodFilename = true; 275 | } 276 | } 277 | 278 | return currentFilePath; 279 | } 280 | } 281 | } -------------------------------------------------------------------------------- /src/Utils/RateLimiter.cs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * PowerPing - Advanced command line ping tool 3 | * Copyright (c) 2024 Matthew Carney [matthewcarney64@gmail.com] 4 | * https://github.com/Killeroo/PowerPing 5 | *************************************************************************/ 6 | 7 | using System.Diagnostics; 8 | 9 | namespace PowerPing 10 | { 11 | /// 12 | /// Ensures that a job can't run more often than a specified interval. For example, 13 | /// in a tight loop that needs to write status updates to the console periodically, 14 | /// this can be used to limit the rate at which the status updates happen. 15 | /// 16 | public class RateLimiter 17 | { 18 | private readonly long _minimumRunIntervalTicks; 19 | private long _lastRunTimestamp; 20 | 21 | public TimeSpan ElapsedSinceLastRun { get; private set; } 22 | 23 | public RateLimiter(TimeSpan minimumRunInterval) 24 | { 25 | _minimumRunIntervalTicks = Helper.TimeSpanToStopwatchTicks(minimumRunInterval.Ticks); 26 | } 27 | 28 | public bool RequestRun() 29 | { 30 | long currentTimestamp = Stopwatch.GetTimestamp(); 31 | long elapsed = _lastRunTimestamp == 0 ? 0 : (currentTimestamp - _lastRunTimestamp); // Will be 0 on first call to this method 32 | if (elapsed == 0 || elapsed >= _minimumRunIntervalTicks) 33 | { 34 | ElapsedSinceLastRun = new TimeSpan(Helper.StopwatchToTimeSpanTicks(elapsed)); 35 | _lastRunTimestamp = currentTimestamp; 36 | return true; 37 | } 38 | return false; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /tests/test-argument-parsing.ps1: -------------------------------------------------------------------------------- 1 | Write-Host "============ test-argument-parsing script ============" -ForegroundColor Yellow 2 | 3 | $global:FailedTestDescriptions = @() 4 | 5 | $script_path = (split-path -parent $MyInvocation.MyCommand.Definition) 6 | 7 | # TODO: Need a way to test different argument characters 8 | 9 | # Executable locations 10 | $powerping_x64_location = (split-path -parent $MyInvocation.MyCommand.Definition).ToString() + "\src\bin\Release\net6.0\PowerPing.exe" 11 | # $powerping_x86_location = (split-path -parent $MyInvocation.MyCommand.Definition).ToString() + "\build\x86\PowerPing.exe" 12 | 13 | # Remove tests directoy from the path 14 | $seperator = [IO.Path]::DirectorySeparatorChar 15 | # $powerping_x86_location = $powerping_x86_location.replace($seperator + "tests" + $seperator, $seperator) 16 | $powerping_x64_location = $powerping_x64_location.replace($seperator + "tests" + $seperator, $seperator) 17 | 18 | # Structure to store test results 19 | $global:stats = @{ 20 | TestsPerformed = [uint64] 0; 21 | TestsPassed = [uint64] 0; 22 | TestsFailed = [uint64] 0; 23 | } 24 | 25 | # Function for running test 26 | function Run-Test($description, $arguments, [int]$returnCode) 27 | { 28 | Write-Host($description) -NoNewline -ForegroundColor White 29 | Write-Host(" (`"" + $arguments + "`") expects") -NoNewline 30 | Write-Host(" [" + $returnCode + "]: ") -NoNewline -ForegroundColor Magenta 31 | 32 | Write-Host("[x64]") -NoNewline -ForegroundColor Yellow 33 | $stats.TestsPerformed += 1 34 | $Result = Start-Process -FilePath $powerping_x64_location -ArgumentList ($arguments) -PassThru -Wait 35 | if($Result.ExitCode -eq $returnCode) { 36 | Write-Host(" ==== Test passed ===== ") -ForegroundColor Green 37 | $stats.TestsPassed += 1 38 | } else { 39 | Write-Host(" --- Test Failed --- ") -ForegroundColor Red 40 | $stats.TestsFailed += 1 41 | } 42 | 43 | # Write-Host("[x86]") -NoNewline -ForegroundColor Yellow 44 | # $stats.TestsPerformed += 1 45 | # $Result = Start-Process -FilePath $powerping_x86_location -ArgumentList ($arguments) -PassThru -Wait 46 | # if($Result.ExitCode -eq $returnCode) { 47 | # Write-Host(" ==== Test passed =====") -ForegroundColor Green 48 | # $stats.TestsPassed += 1 49 | # } else { 50 | # Write-Host(" --- Test Failed ---") -ForegroundColor Red 51 | # $stats.TestsFailed += 1 52 | # $global:FailedTestDescriptions += $description 53 | # } 54 | } 55 | 56 | Write-Host 57 | Write-Host "Baseline tests" 58 | Write-Host "----------------------" 59 | Run-Test "Test noinput flag works (all tests reply on it)" "-noinput" 1 60 | Run-Test "Test with just address" "8.8.8.8" 0 61 | Run-Test "Run with no arguments or address" " " 0 62 | Run-Test "Run help argument" "-help" 0 63 | Run-Test "Test with no address" "-t" 1 64 | Run-Test "Test with no address (but with agrument paramter" "-c 5" 1 65 | 66 | Write-Host 67 | Write-Host "Invalid arguments" 68 | Write-Host "----------------------" 69 | Run-Test "Test single bad argument" "-badargument" 1 70 | Run-Test "Test 2 bad arguments" "-badargument -anotherbadarg" 1 71 | Run-Test "Test bad and valid argument" "-badargument -t" 1 72 | Run-Test "Test bad and valid argument with parameter" "-badargument -c 5" 1 73 | Run-Test "Test bad and valid arguments with address" "-badargument -c 5 8.8.8.8" 1 74 | Run-Test "Test bad strings with no argument prefix" "bad argument 8.8.8.8" 1 75 | 76 | Write-Host 77 | Write-Host "Address format" 78 | Write-Host "----------------------" 79 | Run-Test "Test valid IPv4 address" "8.8.8.8" 0 80 | Run-Test "Test invalid IPv4 address" "8.8.8.8.8" 1 81 | Run-Test "Test valid IPv4 address with port" "8.8.8.8:0" 1 82 | Run-Test "Test valid url" "google.com" 0 83 | Run-Test "Test invalid url" "thisisnevergoingtobe-reak.com" 1 84 | Run-Test "Test url with path extension" "google.com/something" 1 85 | Run-Test "Test url with file extension" "google.com/test.txt" 1 86 | Run-Test "Test url with path and file extension" "-c 1 google.com/something/test.txt" 1 87 | Run-Test "Test url with protocol" "-c 1 https://google.com" 1 88 | Run-Test "Test full url" "https://en.wikipedia.org/w/index.php?search=harimau&title=Special%3ASearch&go=Go&ns0=1" 1 89 | 90 | Write-Host 91 | Write-Host "Test arguments" 92 | Write-Host "----------------------" 93 | Run-Test "Test `'count`' with full argument" "-count 1 8.8.8.8" 0 94 | Run-Test "Test `'count`' with short argument" "-c 1 8.8.8.8" 0 95 | Run-Test "Test `'count`' with parameter" "-c 1 8.8.8.8" 0 96 | Run-Test "Test `'count`' with missing address" "-c 1" 1 97 | Run-Test "Test `'count`' with empty parameter" "-c 8.8.8.8" 1 98 | Run-Test "Test `'count`' with invalid positive parameter" "-c 100000000000000000000000000000000000000000000000000000000000000000000000000000000000 8.8.8.8" 1 99 | Run-Test "Test `'count`' with invalid negative parameter" "-c -1 8.8.8.8" 1 100 | Run-Test "Test `'limit`' with full argument" "-limit 1 8.8.8.8" 0 101 | Run-Test "Test `'limit`' with short argument" "-l 1 8.8.8.8" 0 102 | Run-Test "Test `'limit`' with paramater" "-l 1 8.8.8.8" 0 103 | Run-Test "Test `'limit`' with missing address" "-l 1" 1 104 | Run-Test "Test `'limit`' with empty parameter" "-l 8.8.8.8" 1 105 | Run-Test "Test `'limit`' with invalid positive parameter" "-l 4 8.8.8.8" 1 106 | Run-Test "Test `'limit`' with invalid negative parameter" "-l -1 8.8.8.8" 1 107 | Run-Test "Test `'decimals`' with full argument" "-decimals 1 8.8.8.8" 0 108 | Run-Test "Test `'decimals`' with short argument" "-dp 1 8.8.8.8" 0 109 | Run-Test "Test `'decimals`' with parameter" "-dp 1 8.8.8.8" 0 110 | Run-Test "Test `'decimals`' with missing address" "-dp 1" 1 111 | Run-Test "Test `'decimals`' with empty argument" "-dp" 1 112 | Run-Test "Test `'decimals`' with empty parameter" "-dp 8.8.8.8" 1 113 | Run-Test "Test `'decimals`' with invalid positive parameter" "-dp 4 8.8.8.8" 1 114 | Run-Test "Test `'decimals`' with invalid negative parameter" "-dp -1 8.8.8.8" 1 115 | Run-Test "Test `'timing`' with full argument" "-timing 4 8.8.8.8" 0 116 | Run-Test "Test `'timing`' with short argument" "-ti 4 8.8.8.8" 0 117 | Run-Test "Test `'timing`' with numeric parameter" "-ti 4 8.8.8.8" 0 118 | Run-Test "Test `'timing`' with string parameter" "-ti polite 8.8.8.8" 0 119 | Run-Test "Test `'timing`' with string parameter in speech marks" "-ti `"polite`" 8.8.8.8" 0 120 | Run-Test "Test `'timing`' with missing address" "-ti 4" 1 121 | Run-Test "Test `'timing`' with empty argument" "-ti 8.8.8.8" 1 122 | Run-Test "Test `'timing`' with invalid string parameter" "-ti blahblah 8.8.8.8" 1 123 | Run-Test "Test `'timing`' with invalid positive numeric parameter" "-ti 8 8.8.8.8" 1 124 | Run-Test "Test `'timing`' with invalid negative numeric parameter" "-ti -1 8.8.8.8" 1 125 | Run-Test "Test `'symbols`' with full argument" "-symbols 8.8.8.8" 0 126 | Run-Test "Test `'symbols`' with short argument" "-sym 8.8.8.8" 0 127 | Run-Test "Test `'symbols`' with no arguments, and address at end" "-sym 8.8.8.8" 0 128 | Run-Test "Test `'symbols`' with no arguments, and address at start" "8.8.8.8 -sym" 0 129 | Run-Test "Test `'symbols`' with arguments, and address at end" "-sym 1 8.8.8.8" 0 130 | Run-Test "Test `'symbols`' with arguments, and address at start" "8.8.8.8 -sym 1" 0 131 | Run-Test "Test `'symbols`' with no arguments and leading argument, with address at start" "-sym -c 1 8.8.8.8" 0 132 | Run-Test "Test `'symbols`' with no arguments and leading argument, with address at end" "8.8.8.8 -sym -c 1 " 0 133 | Run-Test "Test `'symbols`' with argument and leading argument" "-sym 1 -c 1 8.8.8.8" 0 134 | Run-Test "Test `'symbols`' with missing address" "-sym 1" 1 135 | Run-Test "Test `'symbols`' with invalid positive theme number" "-sym 1000 8.8.8.8" 1 136 | Run-Test "Test `'symbols`' with invalid negative theme number" "-sym -1000 8.8.8.8" 1 137 | Run-Test "Test `'symbols`' with valid theme number" "-sym 0 8.8.8.8" 0 138 | Run-Test "Test `'timestamp`' with full argument" "-timestamp 8.8.8.8" 0 139 | Run-Test "Test `'timestamp`' with short argument" "-ts 8.8.8.8" 0 140 | Run-Test "Test `'timestamp`' with no parameter at start" "-ts 8.8.8.8" 0 141 | Run-Test "Test `'timestamp`' with no parameter at end" "8.8.8.8 -ts" 0 142 | Run-Test "Test `'timestamp`' with valid parameter at start" "-ts utc 8.8.8.8" 0 143 | Run-Test "Test `'timestamp`' with valid parameter at end" "8.8.8.8 -ts utc" 0 144 | Run-Test "Test `'timestamp`' with invalid parameter at start" "-ts putc 8.8.8.8" 0 145 | Run-Test "Test `'timestamp`' with invalid parameter at end" "8.8.8.8 -ts putc " 0 146 | Run-Test "Test `'timestamp`' with no parameter and leading argument" "8.8.8.8 -ts -c 2" 0 147 | Run-Test "Test `'timestamp`' with missing address" "-ts utc" 1 148 | Run-Test "Test `'fulltimestamp`' with full argument" "-fulltimestamp 8.8.8.8" 0 149 | Run-Test "Test `'fulltimestamp`' with short argument" "-fts 8.8.8.8" 0 150 | Run-Test "Test `'fulltimestamp`' with no parameter at start" "-fts 8.8.8.8" 0 151 | Run-Test "Test `'fulltimestamp`' with no parameter at start" "-fts 8.8.8.8" 0 152 | Run-Test "Test `'fulltimestamp`' with no parameter at end" "8.8.8.8 -fts" 0 153 | Run-Test "Test `'fulltimestamp`' with valid parameter at start" "-fts utc 8.8.8.8" 0 154 | Run-Test "Test `'fulltimestamp`' with valid parameter at end" "8.8.8.8 -fts utc" 0 155 | Run-Test "Test `'fulltimestamp`' with invalid parameter at start" "-fts putc 8.8.8.8" 1 156 | Run-Test "Test `'fulltimestamp`' with invalid parameter at end" "8.8.8.8 -fts putc " 1 157 | Run-Test "Test `'fulltimestamp`' with no parameter and leading argument" "8.8.8.8 -fts -c 2" 0 158 | Run-Test "Test `'fulltimestamp`' with missing address" "-fts utc" 1 159 | Run-Test "Test `'beep`' with full timestamp" "8.8.8.8 -beep" 0 160 | Run-Test "Test `'beep`' with short timestamp" "8.8.8.8 -b" 0 161 | Run-Test "Test `'beep`' with no parameter at end" "8.8.8.8 -beep" 0 162 | Run-Test "Test `'beep`' with no parameter at end" "8.8.8.8 -beep" 0 163 | Run-Test "Test `'beep`' with no parameter at start" "-beep 8.8.8.8" 0 164 | Run-Test "Test `'beep`' with parameter at end" "8.8.8.8 -beep 2" 0 165 | Run-Test "Test `'beep`' with parameter at start" "-beep 2 8.8.8.8" 0 166 | Run-Test "Test `'beep`' with no parameter and leading argument" "8.8.8.8 -b -c 2" 0 167 | Run-Test "Test `'beep`' with missing address" "-b 2" 1 168 | Run-Test "Test `'beep`' with invalid parameter at end" "8.8.8.8 -beep 3" 1 169 | Run-Test "Test `'beep`' with invalid parameter at start" "-beep 3 8.8.8.8" 1 170 | Run-Test "Test `'beep`' with invalid positive parameter" "-b 100000000000000000000000000000000000000000000000000000000000000000000000000000000000 8.8.8.8" 1 171 | Run-Test "Test `'beep`' with invalid negative parameter" "-b -1 8.8.8.8" 1 172 | 173 | Write-Host 174 | Write-Host "Address location tests" 175 | Write-Host "----------------------" 176 | Run-Test "Test IPv4 at start" "8.8.8.8 -c 1 -ts" 0 177 | Run-Test "Test url at start" "google.com -c 1 -ts" 0 178 | Run-Test "Test IPv4 in middle" "-c 1 8.8.8.8 -ts" 1 179 | Run-Test "Test url in middle" "-c 1 google.com -ts" 1 180 | Run-Test "Test IPv4 at end" "-c 1 -ts 8.8.8.8" 0 181 | Run-Test "Test url at end" "-c 1 -ts google.com" 0 182 | 183 | Write-Host 184 | Write-Host($stats.TestsPerformed.ToString() + " tests performed. " + $stats.TestsPassed.ToString() + " tests passed, " +$stats.TestsFailed.ToString() + " failed.") 185 | if ($stats.TestsFailed -gt 0) { 186 | Write-Warning("One or more tests failed:"); 187 | foreach ($test in $global:FailedTestDescriptions) { 188 | Write-Host("-> ") -NoNewline 189 | Write-Host($test) -ForegroundColor Yellow 190 | } 191 | } 192 | 193 | # Return results to caller script 194 | return $stats.TestsPassed.ToString() + "/" + $stats.TestsPerformed.ToString() -------------------------------------------------------------------------------- /tests/test-core-functions.ps1: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /tests/test-lookup-functions.ps1: -------------------------------------------------------------------------------- 1 | Write-Host "============ test-lookup-functions script ============" -ForegroundColor Yellow 2 | 3 | # Location of script 4 | $script_path = (split-path -parent $MyInvocation.MyCommand.Definition) 5 | 6 | # Locate x64 assembly and load into powershell 7 | # TODO: Test x86 arch as well? 8 | $seperator = [IO.Path]::DirectorySeparatorChar 9 | $powerping_x64_location = (split-path -parent $MyInvocation.MyCommand.Definition).ToString() + "\src\bin\Release\net6.0\PowerPing.exe" 10 | $powerping_x64_location = $powerping_x64_location.replace($seperator + "tests" + $seperator, $seperator) 11 | [Reflection.Assembly]::LoadFile($powerping_x64_location) 12 | 13 | # For storing test results 14 | $global:stats = @{ 15 | TestsPerformed = [uint64] 0; 16 | TestsPassed = [uint64] 0; 17 | TestsFailed = [uint64] 0; 18 | } 19 | 20 | function Run-GetLocalAddress-Test($description) 21 | { 22 | Write-Host "[GetLocalAddress()] " -NoNewline 23 | Write-Host $description -NoNewLine 24 | Write-Host ": " -NoNewline 25 | $powerping_local_address = $powerpingLocalAddress = [PowerPing.Lookup]::GetLocalAddress() 26 | $our_addresses = Get-NetIPAddress 27 | $match = $false 28 | $stats.TestsPerformed += 1 29 | 30 | # Loop through results in powershell 31 | foreach ($address in $our_addresses) { 32 | if ($powerping_local_address -eq $address.IPAddress) { 33 | $match = $true 34 | break 35 | } 36 | } 37 | 38 | # You know in alot of ways powershell accesses the same api as c# so how much of a test really is this 39 | if ($match -eq $true) { 40 | Write-Host "($powerping_local_address)" -BackgroundColor Green -NoNewline 41 | Write-Host " ==== Test passed =====" -ForegroundColor Green 42 | Write-Warning "This could be a virtual adapter address, please check its origin." 43 | } else { 44 | $stats.TestsFailed += 1 45 | Write-Host $powerping_local_address -BackgroundColor Red -NoNewline 46 | Write-Host "--- Test Failed ---" -ForegroundColor Red 47 | } 48 | } 49 | 50 | function Run-GetAddressLocationInfo-NotDetailed-Test($description, $address) 51 | { 52 | Write-Host "[GetAddressLocationInfo()] [NotDetailed] " -NoNewline 53 | Write-Host $description -NoNewLine 54 | Write-Host " ("-NoNewline 55 | Write-Host $address -ForegroundColor Cyan -NoNewline 56 | Write-Host "):" -NoNewline 57 | 58 | $output = [PowerPing.Lookup]::GetAddressLocationInfo($address, $false); 59 | $stats.TestsPerformed += 1 60 | 61 | if ($output.Contains("Location unavaliable")) { 62 | Write-Host(" --- Test Failed ---") -ForegroundColor Red 63 | $stats.TestsFailed += 1 64 | } else { 65 | Write-Host(" ==== Test passed =====") -ForegroundColor Green 66 | $stats.TestsPassed += 1 67 | } 68 | } 69 | 70 | function Run-GetAddressLocationInfo-Detailed-Test($description, $address) 71 | { 72 | Write-Host "[GetAddressLocationInfo()] [Detailed] " -NoNewline 73 | Write-Host $description -NoNewLine 74 | Write-Host " ("-NoNewline 75 | Write-Host $address -ForegroundColor Cyan -NoNewline 76 | Write-Host "):" -NoNewline 77 | 78 | $output = [PowerPing.Lookup]::GetAddressLocationInfo($address, $true); 79 | $stats.TestsPerformed += 1 80 | 81 | if ($output.Contains("Location unavaliable")) { 82 | Write-Host(" --- Test Failed ---") -ForegroundColor Red 83 | $stats.TestsFailed += 1 84 | } else { 85 | Write-Host(" ==== Test passed =====") -ForegroundColor Green 86 | $stats.TestsPassed += 1 87 | } 88 | } 89 | 90 | function Run-QueryDNS-Test($description, $address) 91 | { 92 | Write-Host "[QueryDNS()] " -NoNewline 93 | Write-Host $description -NoNewLine 94 | Write-Host " ("-NoNewline 95 | Write-Host $address -ForegroundColor Cyan -NoNewline 96 | Write-Host "):" -NoNewline 97 | 98 | # See if powerping's returned address is in our list of resolved addresses 99 | $stats.TestsPerformed += 1 100 | $match = $false 101 | $powerping_address = [PowerPing.Lookup]::QueryDNS($address, [Net.Sockets.AddressFamily]::InterNetwork); 102 | $our_addresses = Resolve-DnsName $address 103 | Foreach ($addr in $our_addresses) { 104 | if ($addr.IPAddress -eq $powerping_address) { 105 | $match = $true 106 | } 107 | } 108 | 109 | if ($match -ne $true) { 110 | Write-Host(" --- Test Failed ---") -ForegroundColor Red 111 | $stats.TestsFailed += 1 112 | } else { 113 | Write-Host(" ==== Test passed =====") -ForegroundColor Green 114 | $stats.TestsPassed += 1 115 | } 116 | } 117 | 118 | function Run-QueryHost-Test($description, $address) 119 | { 120 | Write-Host "[QueryHost()] " -NoNewline 121 | Write-Host $description -NoNewline 122 | Write-Host " ("-NoNewline 123 | Write-Host $address -ForegroundColor Cyan -NoNewline 124 | Write-Host "):" -NoNewline 125 | 126 | $stats.TestsPerformed += 1 127 | $our_hostname = Resolve-DnsName $address 128 | $powerping_hostname = [PowerPing.Lookup]::QueryHost($address) 129 | 130 | if ($powerping_hostname -eq $our_hostname.NameHost) { 131 | Write-Host(" ==== Test passed =====") -ForegroundColor Green 132 | $stats.TestsPassed += 1 133 | } else { 134 | Write-Host(" --- Test Failed ---") -ForegroundColor Red 135 | $stats.TestsFailed += 1 136 | } 137 | } 138 | 139 | ## Local address finding 140 | Run-GetLocalAddress-Test "Check local address matches powershell's local address" 141 | 142 | ## Address location functions 143 | Run-GetAddressLocationInfo-NotDetailed-Test "Test valid ip address" "8.8.8.8" 144 | Run-GetAddressLocationInfo-NotDetailed-Test "Test valid ip address" "1.1.1.1" 145 | Run-GetAddressLocationInfo-NotDetailed-Test "Test valid URL" "www.google.com" 146 | Run-GetAddressLocationInfo-NotDetailed-Test "Test valid URL" "en.wikipedia.org" 147 | Run-GetAddressLocationInfo-NotDetailed-Test "Test valid URL" "www.iana.org" 148 | Run-GetAddressLocationInfo-NotDetailed-Test "Test valid URL" "https://en.wikipedia.org" 149 | Run-GetAddressLocationInfo-NotDetailed-Test "Test valid URL" "https://www.google.com" 150 | Run-GetAddressLocationInfo-NotDetailed-Test "Test valid URL" "https://www.iana.org" 151 | Run-GetAddressLocationInfo-NotDetailed-Test "Test full URL" "https://en.wikipedia.org/wiki/Wipeout_(video_game)" 152 | Run-GetAddressLocationInfo-NotDetailed-Test "Test full URL" "https://www.iana.org/domains/idn-tables" 153 | 154 | Run-GetAddressLocationInfo-Detailed-Test "Test valid ip address" "8.8.8.8" 155 | Run-GetAddressLocationInfo-Detailed-Test "Test valid ip address" "1.1.1.1" 156 | Run-GetAddressLocationInfo-Detailed-Test "Test valid URL" "www.google.com" 157 | Run-GetAddressLocationInfo-Detailed-Test "Test valid URL" "en.wikipedia.org" 158 | Run-GetAddressLocationInfo-Detailed-Test "Test valid URL" "www.iana.org" 159 | Run-GetAddressLocationInfo-Detailed-Test "Test valid URL" "https://en.wikipedia.org" 160 | Run-GetAddressLocationInfo-Detailed-Test "Test valid URL" "https://www.google.com" 161 | Run-GetAddressLocationInfo-Detailed-Test "Test valid URL" "https://www.iana.org" 162 | Run-GetAddressLocationInfo-Detailed-Test "Test full URL" "https://en.wikipedia.org/wiki/Wipeout_(video_game)" 163 | Run-GetAddressLocationInfo-Detailed-Test "Test full URL" "https://www.iana.org/domains/idn-tables" 164 | 165 | ## DNS lookup 166 | Run-QueryDNS-Test "Look up address for URL" "google.com" 167 | Run-QueryDNS-Test "Look up address for URL" "www.iana.org" 168 | Run-QueryDNS-Test "Look up address for URL" "en.wikipedia.org" 169 | Run-QueryDNS-Test "Look up address for URL/DNS" "dns.msftncsi.com" 170 | Run-QueryDNS-Test "Look up address for IPv4 address (this might fail as powerping does not query for ip addresses)" "8.8.8.8" 171 | Run-QueryDNS-Test "Look up address for IPv4 address (this might fail as powerping does not query for ip addresses)" "1.1.1.1" 172 | 173 | ## Reverse DNS lookup 174 | Run-QueryHost-Test "Test valid ip address" "8.8.8.8" 175 | Run-QueryHost-Test "Test valid ip address" "8.8.4.4" 176 | Run-QueryHost-Test "Test valid ip address" "1.1.1.1" 177 | Run-QueryHost-Test "Test valid ip address" "131.107.255.255" 178 | #Write-Host "Going to test a load of load addresses..." -BackgroundColor Yellow 179 | #$our_addresses = Get-NetIPAddress 180 | #Get-NetIPAddress | fl 181 | #return 182 | #foreach ($address in $our_addresses) { 183 | # #Run-QueryHost-Test "Test local address" $address.IPAddress 184 | #} 185 | #Run-QueryHost-Test "Test local address" "192.168.1.5" 186 | 187 | Write-Host 188 | Write-Host($stats.TestsPerformed.ToString() + " tests performed. " + $stats.TestsPassed.ToString() + " tests passed, " +$stats.TestsFailed.ToString() + " failed.") 189 | if ($stats.TestsFailed -gt 0) { 190 | Write-Warning("One or more tests failed."); 191 | } 192 | 193 | # Return results to caller script 194 | return $stats.TestsPassed.ToString() + "/" + $stats.TestsPerformed.ToString() -------------------------------------------------------------------------------- /tests/visual-tests.ps1: -------------------------------------------------------------------------------- 1 | # Some tests you just have to see to believe 2 | # with such complex output I really cba to write tests for them 3 | # also its quite easy to see if there is something wrong with these functions as they are quite visual 4 | 5 | --------------------------------------------------------------------------------