Author SHA1 Message Date
Stone_Red 25b95d7605 Merge pull request #10 from Stone-Red-Code/develop
Develop
2025-06-27 13:06:48 +02:00
Stone_Red e5f69fd256 Update version 2025-06-27 13:06:17 +02:00
Stone_Red a99903eec6 Update nuget packages 2025-06-27 13:03:38 +02:00
Stone_Red e6943924cc Allow files paths without directory as output paths 2025-06-27 13:01:16 +02:00
Stone_Red cb5a4f2fe3 Merge pull request #9 from Stone-Red-Code/develop
Develop
2024-10-30 20:51:05 +01:00
Stone_Red edc7ea95dd Update package manifest files 2024-10-30 20:49:50 +01:00
Stone_Red 4c38d52709 Add missing license in readme and small fixes 2024-10-30 19:00:44 +01:00
Stone_Red c094f0400e Update readme to new version and include banner 2024-10-29 12:30:23 +01:00
Stone_Red 975e7b7d59 Merge pull request #8 from Stone-Red-Code/main
Sync
2024-10-29 12:21:38 +01:00
Stone_Red 7e043c2fe2 Merge branch 'develop' into main 2024-10-29 12:21:27 +01:00
Stone_Red 6e86058862 Set "scan" as default 2024-10-29 12:17:14 +01:00
Stone_Red 1b5278bf09 Add monitoring mode and improve json output format 2024-10-27 01:17:10 +02:00
Stone_Red 701cd8e3fc Add command line arguments
- Save to JSON
- Save to CSV
- Retry
- Concurrency
- Silent
- Help
- Version
2024-10-25 14:45:26 +02:00
Stone_Red ae8055fac8 Add humanizer library 2024-10-25 00:18:05 +02:00
Stone_Red 11a45e1cc6 Add summary to package description 2024-06-21 15:29:57 +02:00
Stone_Red 08b84f3dad Update .nuspec 2024-06-04 20:24:21 +02:00
Stone_Red a06dfe82a8 Update readme 2024-06-04 17:56:02 +02:00
Stone_Red 3c5d165ebb Fix pluralization & alignment of output 2024-05-01 14:58:57 +02:00
Stone_Red 553fd97e2a Update arp-scanner.nuspec 2024-05-01 14:48:11 +02:00
Stone_Red 5ceee1092f Set Private to null instead of false 2024-05-01 02:00:12 +02:00
Stone_Red 9f49f2d595 Merge pull request #7 from Stone-Red-Code/develop
Fix snapcraft.yaml?
2024-05-01 01:51:24 +02:00
Stone_Red 92b1fd0598 Fix snapcraft.yaml? 2024-05-01 01:51:02 +02:00
Stone_Red 6f6a2c2ee5 Merge pull request #6 from Stone-Red-Code/develop
Update snapcraft architecture section
2024-05-01 01:46:29 +02:00
Stone_Red 687b882d5e Update snapcraft architecture section 2024-05-01 01:45:59 +02:00
Stone_Red 95774eaee8 Merge pull request #5 from Stone-Red-Code/develop
Update project to .NET 8 and auto update mac db
2024-05-01 01:37:05 +02:00
Stone_Red 6de6edbf48 Update project to .NET 8 and auto update mac db 2024-05-01 01:36:31 +02:00
Stone_Red dabc878624 Update choco VERIFICATION.txt 2024-02-08 07:58:49 +01:00
Stone_Red 1420655b8d Update snapcraft.yaml 2024-01-09 13:35:34 +01:00
Stone_Red 96b261238e Update choco package nuspec 2024-01-09 13:19:23 +01:00
Stone_Red 89f1401de9 Update README.md
Remove snap connect instructions because it now auto connects.
2024-01-05 14:28:50 +01:00
Stone_Red 924e0016cb Update twitter link in README.md to always show the latest updates 2024-01-03 12:23:41 +01:00
Stone_Red 40cb53d037 Update README.md 2024-01-03 12:22:28 +01:00
Stone_Red a0c2811fae Merge pull request #4 from Stone-Red-Code/develop
Develop
2024-01-03 11:58:46 +01:00
Stone_Red a4536f05d8 Debug build 2024-01-03 11:58:25 +01:00
Stone_Red 38ea2e99c7 Merge branch 'main' into develop 2024-01-03 00:43:04 +01:00
14 changed files with 559 additions and 44676 deletions
+16 -10
View File
@@ -1,26 +1,32 @@
# ARP-Scanner
> A lightweight cross-platform IP scanner
![Arp-Scanner](https://socialify.git.ci/Stone-Red-Code/Arp-Scanner/image?description=1&forks=1&issues=1&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI%2FPgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDIwMDEwOTA0Ly9FTiIKICJodHRwOi8vd3d3LnczLm9yZy9UUi8yMDAxL1JFQy1TVkctMjAwMTA5MDQvRFREL3N2ZzEwLmR0ZCI%2BCjxzdmcgdmVyc2lvbj0iMS4wIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiB3aWR0aD0iMTAwLjAwMDAwMHB0IiBoZWlnaHQ9IjEwMC4wMDAwMDBwdCIgdmlld0JveD0iMCAwIDEwMC4wMDAwMDAgMTAwLjAwMDAwMCIKIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIG1lZXQiPgo8bWV0YWRhdGE%2BCkNyZWF0ZWQgYnkgcG90cmFjZSAxLjEwLCB3cml0dGVuIGJ5IFBldGVyIFNlbGluZ2VyIDIwMDEtMjAxMQo8L21ldGFkYXRhPgogICA8cmVjdCB4PSIwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgcng9IjE1IiBmaWxsPSIjNWE1YTVhIiAvPgo8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLjAwMDAwMCw5NS4wMDAwMDApIHNjYWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiCmZpbGw9IiMwMDAwMDAiIHN0cm9rZT0ibm9uZSI%2BCjxwYXRoIGQ9Ik02NDAgOTIwIGMtOSAtMTcgLTMgLTIzIDQ3IC00OSBsNTggLTMwIC0xMDIgLTEgLTEwMyAwIDAgLTMwIGMwIC0yNQotNCAtMzAgLTI0IC0zMCAtMTQgMCAtMjggLTQgLTMxIC0xMCAtNCAtNiAtNTYgLTEwIC0xMjIgLTEwIC0xNDggMCAtMTg1IC0xMQotMjQ4IC03NSAtNzMgLTcyIC04OSAtMTM1IC02MCAtMjMzIDIwIC02NiA2NiAtMTE5IDEzMCAtMTQ4IDQ4IC0yMyA1OCAtMjQKMzE2IC0yNCAyNTkgMCAyNjcgLTEgMzEwIC0yMyAyNCAtMTMgNTcgLTQzIDc0IC02NiAyNSAtMzYgMzAgLTU0IDMzIC0xMTcgNAotNjUgNyAtNzQgMjMgLTc0IDE2IDAgMTkgOCAxOSA1NiAwIDMxIC01IDc1IC0xMiA5OCAtMTYgNTUgLTk5IDEzOCAtMTU0IDE1NAotMjggOCAtMTI2IDEyIC0zMDEgMTIgLTI1MyAwIC0yNjEgMSAtMzA0IDIzIC0yNCAxMyAtNTcgNDMgLTc0IDY2IC0yNiAzNyAtMzAKNTEgLTMwIDExMSAwIDYwIDQgNzQgMzAgMTExIDE3IDIzIDUwIDUzIDc0IDY2IDM5IDIwIDU4IDIzIDE2NyAyMyA3MSAwIDEyNQotNCAxMjkgLTEwIDMgLTUgMTcgLTEwIDMwIC0xMCAxOCAwIDI0IC02IDI3IC0yNyBsMyAtMjggMTk4IC0zIDE5NyAtMiAwIDEwMAowIDEwMCAtNTkgMCBjLTUzIDAgLTY5IDUgLTE0NSA0OSAtODEgNDcgLTg2IDQ5IC05NiAzMXogbTIxMCAtMTA5IGMzOSAtNSA0NQotMTAgNTMgLTM4IDUgLTE3IDYgLTQ4IDMgLTY4IGwtNyAtMzYgLTE1NiAyIC0xNTYgMSAtMzIgMzQgLTMyIDM0IDMzIDM1IGMzMQozMyAzNyAzNSA5NiAzNSAzNSAwIDcwIDUgNzggMTAgOCA2IDI5IDggNDUgNCAxNyAtNCA1MCAtOSA3NSAtMTN6Ii8%2BCjxwYXRoIGQ9Ik0zMjAgNjE5IGMtNTYgLTI1IC0xNTAgLTk3IC0xNTAgLTExNSAwIC0xNyA5MiAtODcgMTUwIC0xMTMgNjAgLTI3CjE1MCAtMjcgMjEwIDAgNTUgMjUgMTUwIDk3IDE1MCAxMTQgMCAxNyAtOTUgODkgLTE1MCAxMTQgLTYwIDI3IC0xNTAgMjcgLTIxMAoweiBtMTkyIC0yOSBjMjggLTM0IDIzIC04NCAtMTEgLTEyMiAtNDMgLTQ4IC0xMDkgLTQ4IC0xNTIgMCAtNDQgNDkgLTM3IDEyMQoxNCAxNDIgMTIgNiA0NiA4IDc2IDYgNDIgLTIgNTkgLTggNzMgLTI2eiBtLTIxMCAtNDkgYy00IC00NSAtMiAtNTIgMzEgLTg3CjMzIC0zNiAzOSAtMzkgOTIgLTM5IDUzIDAgNTkgMyA5MiAzOSAzMyAzNSAzNSA0MiAzMSA4NyBsLTUgNDkgMjYgLTE2IGMxNCAtOQozOCAtMjggNTQgLTQyIGwyNyAtMjYgLTI3IC0yOCBjLTE1IC0xNiAtNTQgLTQyIC04NyAtNTggLTgwIC00MSAtMTQyIC00MQotMjIyIDAgLTU0IDI3IC0xMjQgODQgLTExMyA5MyAxMyAxMiAxMDEgNzcgMTAzIDc3IDIgMCAxIC0yMiAtMiAtNDl6Ii8%2BCjxwYXRoIGQ9Ik0zODQgNTY1IGMtMTkgLTI5IC0xMiAtNTcgMjAgLTc0IDI4IC0xNiA2MSAtMiA3MSAyOSA5IDMwIC0yMCA3MAotNTAgNzAgLTE1IDAgLTMxIC0xMCAtNDEgLTI1eiIvPgo8cGF0aCBkPSJNMjc2IDIyMiBjLTMgLTUgLTE4IC00NSAtMzUgLTkwIC0yMSAtNTcgLTI2IC04MiAtMTcgLTgyIDcgMCAxOCAxMQoyNCAyNCAxNSAzNCA2OSAzMyA4NSAtMSA5IC0yMCA0NCAtMzUgMzUgLTE1IC0xIDQgLTE2IDQ1IC0zMyA5MCAtMjcgNzQgLTQ1Cjk3IC01OSA3NHogbTI4IC01NiBjMyAtMTMgOCAtMzEgMTEgLTM4IDUgLTExIC0xIC0xNCAtMjMgLTEwIC0yOSA0IC0yOSA1IC0xOQozOCAxMiA0MCAyMiA0MyAzMSAxMHoiLz4KPHBhdGggZD0iTTM5OSAyMjggYy0xIC0yIC0zIC00MiAtNCAtOTAgLTIgLTY5IDAgLTg4IDExIC04OCAxMCAwIDE0IDEzIDE0IDQxCjAgMzMgMyA0MCAxOCAzNyA5IC0yIDI1IC0yMCAzNSAtNDAgMTAgLTIxIDI1IC0zOCAzNCAtMzggMTIgMCA5IDEwIC0xMSA0NQotMjEgMzggLTIzIDQ3IC0xMSA1MiAyMiA4IDE5IDYwIC00IDczIC0xNyA5IC03OSAxNSAtODIgOHogbTcxIC0zMyBjMTggLTIxIDUKLTQ1IC0yNSAtNDUgLTIxIDAgLTI1IDUgLTI1IDMwIDAgMjMgNSAzMCAxOSAzMCAxMCAwIDI0IC03IDMxIC0xNXoiLz4KPHBhdGggZD0iTTU1MCAxNDAgYzAgLTUzIDQgLTkwIDEwIC05MCA2IDAgMTAgMTYgMTAgMzUgMCAzMiAyIDM1IDI5IDM1IDMwIDAKNjEgMzAgNjEgNTcgMCAyNSAtMzkgNTMgLTc1IDUzIGwtMzUgMCAwIC05MHogbTY0IDU3IGMyNiAtMTkgMTcgLTUxIC0xNyAtNTUKLTI1IC0zIC0yNyAtMSAtMjcgMzIgMCAzOSAxMyA0NSA0NCAyM3oiLz4KPC9nPgo8L3N2Zz4K&name=1&pattern=Signal&pulls=1&stargazers=1&theme=Auto)
## Usage
1. Download the latest release
1. Download & install the latest release
- Chocolatey (Windows): `choco install arp-scanner`
- Snapcraft (Linux): `snap install arp-scanner`\
and `snap connect arp-scanner:network-control` to give it permission to send arp requests
- GitHub (Windows/Linux): https://github.com/Stone-Red-Code/ARP-Scanner/releases
1. Start `arp-scanner` with the IP range it should scan as parameter (e.g. `192.168.1.0 - 192.168.1.255`)
- Snapcraft (Linux): `snap install arp-scanner`
- GitHub (Windows/Linux): [releases](https://github.com/Stone-Red-Code/ARP-Scanner/releases)
1. Use the `arp-scanner` command
- `arp-scanner help` lists all available commands
- `arp-scanner scan` scans the network for devices
- `arp-scanner scan --help` lists all available options for the scan command
- `arp-scanner monitor` continuously scans the network for devices
- `arp-scanner monitor --help` lists all available options for the monitor command
> [!Note]
> This programm may not work on all linux distributions because the [ArpLookup](https://github.com/georg-jung/ArpLookup) library has some [limitations](https://github.com/georg-jung/ArpLookup#supported-platforms)
## Follow the development
[![Twitter(X)](http://img.shields.io/badge/Twitter-black.svg?&logo=x&style=for-the-badge&logoColor=white)](https://twitter.com/search?q=%23ArpScanner%20%40StoneRedCode)
[![Twitter(X)](http://img.shields.io/badge/Twitter-black.svg?&logo=x&style=for-the-badge&logoColor=white)](https://twitter.com/search?q=%23ArpScanner%20%40StoneRedCode&f=live)
## Example
Start `arp-scanner` with the IP range it should scan as parameter (e.g. `192.168.1.0-192.168.1.255`)
![ARP-Scanner-Example](https://user-images.githubusercontent.com/56473591/236293969-4e8a65d2-86a3-4f10-8837-2b1aa0490252.png)
## Third party licenses
- [ArpLookup](https://github.com/georg-jung/ArpLookup) - [MIT](https://github.com/georg-jung/ArpLookup/blob/master/LICENSE.txt)
- [IPAddressRange](https://github.com/jsakamoto/ipaddressrange) - [MPL-2.0](https://github.com/jsakamoto/ipaddressrange/blob/master/LICENSE)
- [Humanizer](https://github.com/Humanizr/Humanizer) - [MIT](https://github.com/Humanizr/Humanizer/blob/main/license.txt)
+17 -12
View File
@@ -40,7 +40,7 @@ enclosed in quotation marks, you should use an editor that supports UTF-8, not t
<!-- Note that unstable versions like 0.0.1 can be considered a released version, but it's
possible that one can release a 0.0.1-beta before you release a 0.0.1 version. If the version
number is final, that is considered a released version and not a prerelease. -->
<version>0.2.0.20240103</version>
<version>1.1.0.20250627</version>
<!-- <packageSourceUrl>Where is this Chocolatey package located (think GitHub)? packageSourceUrl
is highly recommended for the community feed</packageSourceUrl>-->
<!-- owners is a poor name for maintainers of the package. It sticks around by this name for
@@ -67,26 +67,31 @@ enclosed in quotation marks, you should use an editor that supports UTF-8, not t
<!--<docsUrl>At
what url are the software docs located?</docsUrl>-->
<!--<mailingListUrl></mailingListUrl>-->
<!--<bugTrackerUrl></bugTrackerUrl>-->
<tags>arp-scanner arp p2p fiel-transfer</tags>
<bugTrackerUrl>https://github.com/Stone-Red-Code/ARP-Scanner/issues</bugTrackerUrl>
<tags>arp-scanner arp</tags>
<summary>A lightweight cross-platform IP scanner</summary>
<description>
**Usage**
1. Download the latest release
- Chocolatey (Windows): `choco install arp-scanner --version=0.2.0`
- Snapcraft (Linux): `snap install arp-scanner`\
and `snap connect arp-scanner:network-control` to give it permission to send arp requests
- GitHub (Windows/Linux): https://github.com/Stone-Red-Code/ARP-Scanner/releases
1. Start `arp-scanner` with the IP range it should scan as parameter (e.g. `192.168.1.0 - 192.168.1.255`)
1. Download &amp; install the latest release
- Chocolatey (Windows): `choco install arp-scanner`
- Snapcraft (Linux): `snap install arp-scanner`
- GitHub (Windows/Linux): releases (https://github.com/Stone-Red-Code/ARP-Scanner/releases)
2. Use the `arp-scanner` command
- `arp-scanner help` lists all available commands
- `arp-scanner scan` scans the network for devices
- `arp-scanner scan --help` lists all available options for the scan command
- `arp-scanner monitor` continuously scans the network for devices
- `arp-scanner monitor --help` lists all available options for the monitor command
&gt; Note: This programm may not work on all linux distributions because the [ArpLookup](https://github.com/georg-jung/ArpLookup) library has some [limitations](https://github.com/georg-jung/ArpLookup#supported-platforms)
Note: This programm may not work on all linux distributions because the ArpLookup (https://github.com/georg-jung/ArpLookup) library has some limitations (https://github.com/georg-jung/ArpLookup#supported-platforms)
**Third party licenses**
- ArpLookup (https://github.com/georg-jung/ArpLookup) - MIT (https://github.com/georg-jung/ArpLookup/blob/master/LICENSE.txt)
- IPAddressRange (https://github.com/jsakamoto/ipaddressrange) - MPL-2.0 (https://github.com/jsakamoto/ipaddressrange/blob/master/LICENSE)
- Humanizer (https://github.com/Humanizr/Humanizer) - MIT (https://github.com/Humanizr/Humanizer/blob/main/license.txt)
</description>
<!-- <releaseNotes>__REPLACE_OR_REMOVE__MarkDown_Okay</releaseNotes> -->
<releaseNotes>https://github.com/Stone-Red-Code/ARP-Scanner/releases/latest</releaseNotes>
<!-- =============================== -->
<!-- Specifying dependencies and version ranges?
@@ -112,6 +117,6 @@ enclosed in quotation marks, you should use an editor that supports UTF-8, not t
<files>
<!-- this section controls what actually gets packaged into the Chocolatey package -->
<file src="tools\**" target="tools" />
<file src="..\src\ARP-Scanner\bin\Release\net6.0\**" target="tools" />
<file src="..\src\ARP-Scanner\bin\Release\net8.0\**" target="tools" />
</files>
</package>
+4 -2
View File
@@ -2,5 +2,7 @@
Verification is intended to assist the Chocolatey moderators and community
in verifying that this package's contents are trustworthy.
Checksum: c37a793bae228da768c9d4223d12608bfa28decbe7142e9ac19a49795cf23b82b2f0434032b98883343c658276bf2e4423926fbf79c65c48ae6cf11ad48d37ad
Veify using: certutil -hashfile ARP-Scanner.exe sha512
Source Code: https://github.com/Stone-Red-Code/Arp-Scanner
Releases: https://github.com/Stone-Red-Code/ARP-Scanner/releases
Checksum: 551d33306839687dcb226e5290e4bb4d3d5786a9231799e0dba9f9c2a3b4711c346bf2aeb29f4fab54008b6b19c88ebb2649c6545343d41b11bed6b034407374
Verify using: certutil -hashfile C:\ProgramData\Chocolatey\lib\arp-scanner\ARP-Scanner.exe sha512
+16 -12
View File
@@ -1,25 +1,28 @@
name: arp-scanner # you probably want to 'snapcraft register <name>'
version: "0.2.0.20240103" # just for humans, typically '1.2+git' or '1.3.2'
version: "1.1.0.20250627" # just for humans, typically '1.2+git' or '1.3.2'
grade: stable # must be 'stable' to release into candidate/stable channels
summary: A lightweight cross-platform IP scanner # 79 char long summary
description: |
**Usage**
1. Download the latest release
- Chocolatey (Windows): `choco install arp-scanner --version=0.2.0`
- Snapcraft (Linux): `snap install arp-scanner`\
and `snap connect arp-scanner:network-control` to give it permission to send arp requests
- GitHub (Windows/Linux): https://github.com/Stone-Red-Code/ARP-Scanner/releases
1. Start `arp-scanner` with the IP range it should scan as parameter (e.g. `192.168.1.0 - 192.168.1.255`)
1. Download & install the latest release
- Chocolatey (Windows): `choco install arp-scanner`
- Snapcraft (Linux): `snap install arp-scanner`
- GitHub (Windows/Linux): releases (https://github.com/Stone-Red-Code/ARP-Scanner/releases)
2. Use the `arp-scanner` command
- `arp-scanner help` lists all available commands
- `arp-scanner scan` scans the network for devices
- `arp-scanner scan --help` lists all available options for the scan command
- `arp-scanner monitor` continuously scans the network for devices
- `arp-scanner monitor --help` lists all available options for the monitor command
> Note: This programm may not work on all linux distributions because the [ArpLookup](https://github.com/georg-jung/ArpLookup) library has some [limitations](https://github.com/georg-jung/ArpLookup#supported-platforms)
> Note: This programm may not work on all linux distributions because the ArpLookup (https://github.com/georg-jung/ArpLookup) library has some limitations (https://github.com/georg-jung/ArpLookup#supported-platforms)
**Third party licenses**
- ArpLookup (https://github.com/georg-jung/ArpLookup) - MIT (https://github.com/georg-jung/ArpLookup/blob/master/LICENSE.txt)
- IPAddressRange (https://github.com/jsakamoto/ipaddressrange) - MPL-2.0 (https://github.com/jsakamoto/ipaddressrange/blob/master/LICENSE)
- Humanizer (https://github.com/Humanizr/Humanizer) - MIT (https://github.com/Humanizr/Humanizer/blob/main/license.txt)
base: core22 # the base snap is the execution environment for this snap
architectures:
- build-on: amd64
- build-on: arm64
@@ -34,7 +37,7 @@ parts:
dotnet-self-contained-runtime-identifier: linux-x64
source: ./src/ARP-Scanner
build-packages:
- dotnet-sdk-6.0
- dotnet-sdk-8.0
stage-packages:
- libicu70
@@ -42,4 +45,5 @@ apps:
arp-scanner:
command: ARP-Scanner
plugs:
- network-control
- network-observe
- home
+7 -9
View File
@@ -2,22 +2,20 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>ARP_Scanner</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>1.1.0.20250627</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ArpLookup" Version="2.0.3" />
<PackageReference Include="IPAddressRange" Version="6.0.0" />
<PackageReference Include="Stone_Red-C-Sharp-Utilities" Version="1.0.3.1" />
</ItemGroup>
<ItemGroup>
<None Update="mac-vendors.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="CuteUtils" Version="1.1.1" />
<PackageReference Include="Humanizer" Version="2.14.1" />
<PackageReference Include="IPAddressRange" Version="6.2.0" />
</ItemGroup>
</Project>
+53
View File
@@ -0,0 +1,53 @@
namespace ARP_Scanner;
internal class JsonResult
{
public IEnumerable<HostInformation> Hosts { get; set; } = [];
public IEnumerable<HostInformation> NewHosts { get; set; } = [];
public IEnumerable<HostInformation> RemovedHosts { get; set; } = [];
public static JsonResult Parse(IEnumerable<string[]> activeHosts, List<string[]> previousHosts)
{
JsonResult result = new JsonResult
{
Hosts = activeHosts.Select(HostInformation.Parse)
};
if (previousHosts.Count == 0)
{
return result;
}
result.NewHosts = result.Hosts.Where(host => !previousHosts.Exists(previousHost => previousHost[1] == host.Mac));
result.RemovedHosts = previousHosts.Where(previousHost => !result.Hosts.Any(host => host.Mac == previousHost[1])).Select(HostInformation.Parse);
return result;
}
public class HostInformation
{
public required string Ip { get; set; }
public required string Mac { get; set; }
public required string VendorName { get; set; }
public required string BlockType { get; set; }
public bool? Private { get; set; }
public required string LastUpdate { get; set; }
public static HostInformation Parse(string[] row)
{
bool success = bool.TryParse(row[4], out bool isPrivate);
return new HostInformation
{
Ip = row[0],
Mac = row[1],
VendorName = row[2],
BlockType = row[3],
Private = success ? isPrivate : null,
LastUpdate = row[5]
};
}
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace ARP_Scanner;
internal class MacDatabase
{
public DateTime LastUpdate { get; set; }
public List<MacInformation> MacInformations { get; set; } = [];
}
+21
View File
@@ -0,0 +1,21 @@
using System.Text.Json.Serialization;
namespace ARP_Scanner;
internal class MacInformation
{
[JsonPropertyName("macPrefix")]
public required string MacPrefix { get; set; }
[JsonPropertyName("vendorName")]
public required string VendorName { get; set; }
[JsonPropertyName("private")]
public bool? Private { get; set; }
[JsonPropertyName("blockType")]
public required string BlockType { get; set; }
[JsonPropertyName("lastUpdate")]
public required string LastUpdate { get; set; }
}
+93 -29
View File
@@ -1,57 +1,121 @@
using System.Text.RegularExpressions;
using CuteUtils.Misc;
using System.Net.Http.Json;
using System.Text.Json;
namespace ARP_Scanner;
internal class MacVendorLookup
internal partial class MacVendorLookup
{
public readonly string[] header;
private readonly string[][] fields;
private const string macLookupUrl = "https://maclookup.app/downloads/json-database/get-db";
private readonly HttpClient httpClient = new HttpClient() { Timeout = TimeSpan.FromSeconds(20) };
public MacVendorLookup(string csvPath)
private MacDatabase macDatabase = new MacDatabase();
public async Task Initialize(bool silent)
{
if (!File.Exists(csvPath))
if (macDatabase.MacInformations.Count != 0)
{
header = Array.Empty<string>();
fields = Array.Empty<string[]>();
// Already initialized
return;
}
string[] lines = File.ReadAllLines(csvPath);
string cachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "macDatabase.json");
header = lines[0].Split(',');
fields = lines.Skip(1).Select(l => Regex.Split(l, ",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))")).ToArray();
}
// Snap support
string? snapUserCommon = Environment.GetEnvironmentVariable("SNAP_USER_COMMON");
public string[] GetInformation(string macAdress)
{
string[]? data = Array.Find(fields, f => macAdress.StartsWith(f[0]));
if (fields.Length == 0 || header.Length == 0)
if (snapUserCommon is not null)
{
return Array.Empty<string>();
cachePath = Path.Combine(snapUserCommon, "macDatabase.json");
}
else if (data is null)
if (File.Exists(cachePath))
{
string[] result = new string[header.Length - 1];
for (int i = 0; i < result.Length; i++)
try
{
result[i] = "Unknown";
macDatabase = JsonSerializer.Deserialize<MacDatabase>(File.ReadAllText(cachePath)) ?? new MacDatabase();
}
catch (Exception ex)
{
if (!silent)
{
ConsoleExt.WriteLine($"Failed to read MAC database cache: {ex.Message}", ConsoleColor.Red);
}
}
}
// Update MAC database if it's older than a week
if (macDatabase.LastUpdate > DateTime.Now.AddDays(-7))
{
if (!silent)
{
ConsoleExt.WriteLine("Using cached MAC database...", ConsoleColor.DarkYellow);
}
return;
}
if (!silent)
{
ConsoleExt.WriteLine("Downloading MAC database from maclookup.app...", ConsoleColor.DarkYellow);
}
List<MacInformation>? newMacInformation = null;
try
{
newMacInformation = await httpClient.GetFromJsonAsync<List<MacInformation>>(macLookupUrl);
}
catch (Exception ex)
{
if (!silent)
{
ConsoleExt.WriteLine($"Failed to download MAC database: {ex.Message}", ConsoleColor.Red);
}
}
if (newMacInformation is null)
{
if (silent)
{
return;
}
if (macDatabase.MacInformations.Count != 0)
{
ConsoleExt.WriteLine("Failed to download MAC database, using cache...", ConsoleColor.DarkYellow);
}
else
{
ConsoleExt.WriteLine("Failed to download MAC database and no cache found, using empty database...", ConsoleColor.Red);
}
return result;
}
else
{
List<string> result = new List<string>();
for (int i = 1; i < header.Length; i++)
if (!silent)
{
result.Add($"{data[i]}");
ConsoleExt.WriteLine("MAC database downloaded successfully!", ConsoleColor.Green);
}
return result.ToArray();
_ = Directory.CreateDirectory(Path.GetDirectoryName(cachePath)!);
macDatabase.MacInformations = newMacInformation;
macDatabase.LastUpdate = DateTime.Now;
File.WriteAllText(cachePath, JsonSerializer.Serialize(macDatabase));
}
}
public string[] GetHeader()
public MacInformation GetInformation(string macAddress)
{
return header!.Skip(1).ToArray();
macAddress = macAddress.Replace("-", ":")[..8].ToUpper();
return macDatabase.MacInformations.Find(m => m.MacPrefix == macAddress) ?? new MacInformation()
{
MacPrefix = macAddress,
VendorName = "Unknown",
BlockType = "Unknown",
Private = null,
LastUpdate = "Unknown"
};
}
}
+10
View File
@@ -0,0 +1,10 @@
using CommandLine;
namespace ARP_Scanner;
[Verb("monitor", HelpText = "Continuously monitor the specified IP range.")]
internal class MonitorOptions : ScanOptions
{
[Option('d', "delay", Required = false, Default = 60, HelpText = "The delay between each scan in seconds.")]
public int Delay { get; set; }
}
+289 -34
View File
@@ -1,94 +1,349 @@
using ArpLookup;
using CommandLine;
using CuteUtils.Misc;
using Humanizer;
using Humanizer.Localisation;
using NetTools;
using Stone_Red_Utilities.CollectionExtentions;
using Stone_Red_Utilities.ConsoleExtentions;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Net;
using System.Net.NetworkInformation;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ARP_Scanner;
internal static class Program
{
private static async Task Main(string[] args)
{
MacVendorLookup macVendorLookup = new MacVendorLookup("mac-vendors.csv");
private static readonly MacVendorLookup macVendorLookup = new();
private static readonly List<string[]> previouslyActiveHosts = [];
private static readonly JsonSerializerOptions jsonSerializerOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
private static async Task<int> Main(string[] args)
{
if (!Arp.IsSupported)
{
ConsoleExt.WriteLine("ARP is not supported on this platform!", ConsoleColor.Red);
return;
return 1;
}
if (!IPAddressRange.TryParse(string.Join("", args), out IPAddressRange ipAddressRange))
return await Parser.Default.ParseArguments<ScanOptions, MonitorOptions>(args)
.MapResult(
(MonitorOptions monitorOptions) => StartMonitor(monitorOptions),
(ScanOptions scanOptions) => StartScan(scanOptions),
HandleParseError);
}
private static async Task<int> StartMonitor(MonitorOptions options)
{
ConsoleExt.WriteLine($"Monitoring IP range '{options.IpRange}' every {options.Delay} seconds...", ConsoleColor.DarkYellow);
while (true)
{
int exitCode = await StartScan(options);
if (exitCode != 0)
{
return exitCode;
}
Console.WriteLine();
Stopwatch stopwatch = Stopwatch.StartNew();
bool printed = false;
while (stopwatch.Elapsed < TimeSpan.FromSeconds(options.Delay))
{
TimeSpan remainingTime = TimeSpan.FromSeconds(options.Delay) - stopwatch.Elapsed;
Console.Write($"\rNext scan in: {remainingTime.Humanize(3, minUnit: TimeUnit.Second)} ");
await Task.Delay(1000);
printed = true;
}
if (printed)
{
Console.Write($"\r{new string(' ', Console.WindowWidth)}\r");
}
}
}
private static async Task<int> StartScan(ScanOptions options)
{
if (!IPAddressRange.TryParse(options.IpRange, out IPAddressRange ipAddressRange))
{
ConsoleExt.WriteLine("Invalid IP range!", ConsoleColor.Red);
return;
return 2;
}
await macVendorLookup.Initialize(options.Silent);
long ipAddressesCount = ipAddressRange.Count();
long processedIpAddressesCount = 0;
int numberOfDigits = ipAddressesCount.ToString().Length;
int exitCode = 0;
List<string> header = new List<string>() { "IP", "MAC" };
ConcurrentBag<string[]> activeHosts = new ConcurrentBag<string[]>();
ConcurrentBag<string[]> activeHosts = [];
header.AddRange(macVendorLookup.GetHeader());
// If you want to change the header, you also need to change the JsonResult class
List<string> header = [
"IP",
"MAC",
nameof(MacInformation.VendorName).Humanize(LetterCasing.Title),
nameof(MacInformation.BlockType).Humanize(LetterCasing.Title),
nameof(MacInformation.Private).Humanize(LetterCasing.Title),
nameof(MacInformation.LastUpdate).Humanize(LetterCasing.Title)];
ConsoleExt.WriteLine("Starting scan...", ConsoleColor.DarkYellow);
if (!options.Silent)
{
ConsoleExt.WriteLine("Starting scan...", ConsoleColor.DarkYellow);
}
await Parallel.ForEachAsync(ipAddressRange, async (IPAddress ipAddress, CancellationToken _) =>
ParallelOptions parallelOptions = new()
{
MaxDegreeOfParallelism = options.Concurrency
};
await Parallel.ForEachAsync(ipAddressRange, parallelOptions, async (IPAddress ipAddress, CancellationToken _) =>
{
PhysicalAddress? mac = null;
bool fail = false;
try
int retry = options.Retry;
do
{
mac = await Arp.LookupAsync(ipAddress);
}
catch
{
fail = true;
try
{
mac = await Arp.LookupAsync(ipAddress);
fail = false;
}
catch (Exception ex)
{
if (!options.Silent)
{
ConsoleExt.WriteLine($"Failed to lookup MAC address for {ipAddress}: {ex.Message}", ConsoleColor.Red);
}
fail = true;
}
}
while (retry-- > 0 && (mac is null || fail));
long localProcessedIpAddressesCount = Interlocked.Increment(ref processedIpAddressesCount);
if (mac is not null && Array.Exists(mac.GetAddressBytes(), b => b != 0))
{
string formattedMac = BitConverter.ToString(mac.GetAddressBytes());
List<string> info = new List<string> { ipAddress.ToString(), formattedMac };
info.AddRange(macVendorLookup.GetInformation(formattedMac));
ConsoleExt.WriteLine($"Progress: {localProcessedIpAddressesCount.ToString().PadLeft(numberOfDigits)}/{ipAddressesCount} [{100d / ipAddressesCount * localProcessedIpAddressesCount,6:##0.00}%] | Active: {ipAddress}", ConsoleColor.Green);
activeHosts.Add(info.ToArray());
MacInformation macInformation = macVendorLookup.GetInformation(formattedMac);
List<string> info = [ipAddress.ToString(), formattedMac, macInformation.VendorName, macInformation.BlockType, macInformation.Private?.ToString() ?? "Unknown", macInformation.LastUpdate];
if (!options.Silent)
{
ConsoleExt.WriteLine($"Progress: {localProcessedIpAddressesCount.ToString().PadLeft(numberOfDigits)}/{ipAddressesCount} [{100d / ipAddressesCount * localProcessedIpAddressesCount,6:##0.00}%] | Active: {ipAddress}", ConsoleColor.Green);
}
activeHosts.Add([.. info]);
}
else if (fail)
{
ConsoleExt.WriteLine($"Progress: {localProcessedIpAddressesCount.ToString().PadLeft(numberOfDigits)}/{ipAddressesCount} [{100d / ipAddressesCount * localProcessedIpAddressesCount,6:##0.00}%] | Failed: {ipAddress}", ConsoleColor.Red);
if (!options.Silent)
{
ConsoleExt.WriteLine($"Progress: {localProcessedIpAddressesCount.ToString().PadLeft(numberOfDigits)}/{ipAddressesCount} [{100d / ipAddressesCount * localProcessedIpAddressesCount,6:##0.00}%] | Failed: {ipAddress}", ConsoleColor.Red);
}
}
else
{
ConsoleExt.WriteLine($"Progress: {localProcessedIpAddressesCount.ToString().PadLeft(numberOfDigits)}/{ipAddressesCount} [{100d / ipAddressesCount * localProcessedIpAddressesCount,6:##0.00}%] | Inactive: {ipAddress}", ConsoleColor.Red);
if (!options.Silent)
{
ConsoleExt.WriteLine($"Progress: {localProcessedIpAddressesCount.ToString().PadLeft(numberOfDigits)}/{ipAddressesCount} [{100d / ipAddressesCount * localProcessedIpAddressesCount,6:##0.00}%] | Inactive: {ipAddress}", ConsoleColor.Red);
}
}
});
if (!activeHosts.IsEmpty)
if (previouslyActiveHosts.Count > 0 && !options.Silent)
{
Console.WriteLine(Environment.NewLine + "Active hosts:");
PrintDifference(activeHosts, header);
}
else if (!options.Silent)
{
PrintActiveHosts(activeHosts, header);
}
List<string[]>? activeHostsTable = activeHosts.ToList();
if (!options.Silent && (options.JsonPath is not null || options.CsvPath is not null))
{
Console.WriteLine();
}
activeHostsTable.Insert(0, header.ToArray());
if (options.JsonPath is not null)
{
string jsonPath = GetOutputPath(options.JsonPath, ".json");
activeHostsTable.ToArray().To2D().PrintTable(TableStyle.List);
try
{
if (!string.IsNullOrWhiteSpace(Path.GetDirectoryName(jsonPath)))
{
_ = Directory.CreateDirectory(Path.GetDirectoryName(jsonPath)!);
}
ConsoleExt.WriteLine($"{Environment.NewLine}Found {activeHosts.Count} active hosts", ConsoleColor.Green);
string json = JsonSerializer.Serialize(JsonResult.Parse([.. activeHosts], previouslyActiveHosts), jsonSerializerOptions);
File.WriteAllText(jsonPath, json);
if (!options.Silent)
{
ConsoleExt.WriteLine($"Saved JSON to '{jsonPath}'", ConsoleColor.Green);
}
}
catch (Exception ex)
{
if (!options.Silent)
{
ConsoleExt.WriteLine($"Failed to save JSON to '{jsonPath}': {ex.Message}", ConsoleColor.Red);
}
exitCode = 3;
}
}
if (options.CsvPath is not null)
{
string csvPath = GetOutputPath(options.CsvPath, ".csv");
List<string[]>? activeHostsTable = [.. activeHosts];
activeHostsTable.Insert(0, [.. header]);
try
{
if (!string.IsNullOrWhiteSpace(Path.GetDirectoryName(csvPath)))
{
_ = Directory.CreateDirectory(Path.GetDirectoryName(csvPath)!);
}
File.WriteAllLines(csvPath, activeHostsTable.Select(row => string.Join(",", row)));
if (!options.Silent)
{
ConsoleExt.WriteLine($"Saved CSV to '{csvPath}'", ConsoleColor.Green);
}
}
catch (Exception ex)
{
if (!options.Silent)
{
ConsoleExt.WriteLine($"Failed to save CSV to '{csvPath}': {ex.Message}", ConsoleColor.Red);
}
exitCode = 3;
}
}
previouslyActiveHosts.Clear();
foreach (string[] activeHost in activeHosts)
{
previouslyActiveHosts.Add(activeHost);
}
return exitCode;
}
private static Task<int> HandleParseError(IEnumerable<Error> errors)
{
if (errors.IsHelp() || errors.IsVersion())
{
return Task.FromResult(0);
}
else
{
ConsoleExt.WriteLine($"{Environment.NewLine}No active hosts found", ConsoleColor.Red);
return Task.FromResult(1);
}
}
private static string GetOutputPath(string path, string extension)
{
string directory = Path.GetDirectoryName(path) ?? string.Empty;
string fileName = Path.GetFileNameWithoutExtension(path);
string fileExtension = Path.GetExtension(path);
fileExtension = string.IsNullOrEmpty(fileExtension) ? extension : fileExtension;
string dateTime = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
string newPath = Path.Combine(directory, $"{fileName}_{dateTime}{fileExtension}");
int counter = 0;
while (File.Exists(newPath))
{
newPath = Path.Combine(directory, $"{fileName}_{dateTime}_{++counter}{fileExtension}");
}
return newPath;
}
private static void PrintActiveHosts(ConcurrentBag<string[]> activeHosts, List<string> header)
{
List<string[]>? activeHostsTable = [.. activeHosts];
activeHostsTable.Insert(0, [.. header]);
Console.WriteLine();
if (!activeHosts.IsEmpty)
{
Console.WriteLine($"Active hosts:");
activeHostsTable.ToArray().To2D().PrintTable(TableStyle.List);
ConsoleExt.WriteLine($"{Environment.NewLine}Found {"active host".ToQuantity(activeHosts.Count)}", ConsoleColor.Green);
}
else
{
ConsoleExt.WriteLine($"No active hosts found", ConsoleColor.Red);
}
}
private static void PrintDifference(ConcurrentBag<string[]> activeHosts, List<string> header)
{
List<string[]> newHosts = activeHosts.Where(activeHost => !previouslyActiveHosts.Exists(previousHost => previousHost[1] == activeHost[1])).ToList();
List<string[]> removedHosts = previouslyActiveHosts.Where(previousHost => !activeHosts.Any(activeHost => activeHost[1] == previousHost[1])).ToList();
List<string[]>? newHostsTable = [.. newHosts];
newHostsTable.Insert(0, [.. header]);
List<string[]>? removedHostsTable = [.. removedHosts];
removedHostsTable.Insert(0, [.. header]);
Console.WriteLine();
if (newHosts.Count > 0)
{
Console.WriteLine($"New hosts:");
newHostsTable.ToArray().To2D().PrintTable(TableStyle.List);
ConsoleExt.WriteLine($"{Environment.NewLine}Found {"active host".ToQuantity(activeHosts.Count)}", ConsoleColor.Green);
}
else
{
ConsoleExt.WriteLine($"No new hosts found", ConsoleColor.Blue);
}
Console.WriteLine();
if (removedHosts.Count > 0)
{
// Better term for removed host?
Console.WriteLine($"Previously active hosts:");
removedHostsTable.ToArray().To2D().PrintTable(TableStyle.List);
ConsoleExt.WriteLine($"{Environment.NewLine}Found {"previously active host".ToQuantity(removedHosts.Count)}", ConsoleColor.Red);
}
else
{
ConsoleExt.WriteLine($"No previously active hosts are currently inactive", ConsoleColor.Blue);
}
}
}
@@ -2,7 +2,7 @@
"profiles": {
"ARP-Scanner": {
"commandName": "Project",
"commandLineArgs": "192.168.1.0 - 192.168.1.255"
"commandLineArgs": "scan 192.168.1.239"
}
}
}
+25
View File
@@ -0,0 +1,25 @@
using CommandLine;
namespace ARP_Scanner;
[Verb("scan", true, HelpText = "Scan the specified IP range.")]
internal class ScanOptions
{
[Value(0, Required = true, MetaName = "IP range", HelpText = "The IP range to scan.")]
public required string IpRange { get; set; }
[Option('s', "silent", Required = false, HelpText = "Don't print anything to the console.")]
public bool Silent { get; set; }
[Option('r', "retry", Required = false, Default = 0, HelpText = "The number of retries for each ARP request.")]
public int Retry { get; set; }
[Option('c', "concurrency", Required = false, Default = -1, HelpText = "The number of concurrent ARP requests.")]
public int Concurrency { get; set; }
[Option("json", Required = false, HelpText = "The path to the JSON file to save the results.")]
public string? JsonPath { get; set; }
[Option("csv", Required = false, HelpText = "The path to the CSV file to save the results.")]
public string? CsvPath { get; set; }
}
File diff suppressed because it is too large Load Diff