├── requirements.txt
├── .github
└── FUNDING.yml
├── LICENSE
├── README.md
└── netprobe.py
/requirements.txt:
--------------------------------------------------------------------------------
1 | argparse==1.4.0
2 | rich==10.9.0
3 | scapy==2.4.5
4 | socket==0.1.1
5 | table==0.0.9
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: HalilDeniz
4 | patreon: denizhalil
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Halil Ibrahim Deniz
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
13 | all 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
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NetProbe: Network Probe
2 |
3 | **NetProbe** is a tool you can use to scan for devices on your network. The program sends ARP requests to any IP address on your network and lists the IP addresses, MAC addresses, manufacturers, and device models of the responding devices.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ## Features
12 |
13 | - Scan for devices on a specified IP address or subnet
14 | - Display the IP address, MAC address, manufacturer, and device model of discovered devices
15 | - Live tracking of devices (optional)
16 | - Save scan results to a file (optional)
17 | - Filter by manufacturer (e.g., 'Apple') (optional)
18 | - Filter by IP range (e.g., '192.168.1.0/24') (optional)
19 | - Scan rate in seconds (default: 5) (optional)
20 |
21 |
22 | ## Contact
23 | If you have any questions, suggestions, or feedback about the program, please feel free to reach out to me through any of the following platforms:
24 |
25 |
26 |
27 | - Mywebsite: [Denizhalil](https://denizhalil.com)
28 | - LinkedIn: [LinkedIn](https://www.linkedin.com/in/halil-ibrahim-deniz/)
29 | - TryHackMe: [TryHackMe](https://tryhackme.com/p/halilovic)
30 | - Instagram: [Instagram](https://www.instagram.com/deniz.halil333/)
31 | - YouTube: [YouTube](https://www.youtube.com/c/HalilDeniz)
32 | - Email: halildeniz313@gmail.com
33 |
34 |
35 | ## License
36 |
37 | This program is released under the MIT [LICENSE](LICENSE). See LICENSE for more information.
38 |
39 | ## 💰 You can help me by Donating
40 | Thank you for considering supporting me! Your support enables me to dedicate more time and effort to creating useful tools like DNSWatch and developing new projects. By contributing, you're not only helping me improve existing tools but also inspiring new ideas and innovations. Your support plays a vital role in the growth of this project and future endeavors. Together, let's continue building and learning. Thank you!"
41 | [](https://buymeacoffee.com/halildeniz)
42 | [](https://patreon.com/denizhalil)
43 |
44 |
45 |
--------------------------------------------------------------------------------
/netprobe.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import argparse
4 | from rich.console import Console
5 | from rich.table import Table
6 | from scapy.all import ARP, Ether, srp
7 | import requests
8 | import threading
9 | import time
10 | import ipaddress
11 |
12 | class NetProbe:
13 | def __init__(self):
14 | self.console = Console()
15 |
16 | def scan(self, target, iface):
17 | arp = ARP(pdst=target)
18 | ether = Ether(dst="ff:ff:ff:ff:ff:ff")
19 | packet = ether / arp
20 | try:
21 | result = srp(packet, timeout=3, verbose=0, iface=iface)[0]
22 | except PermissionError:
23 | self.console.print("[bold red]Error:[/] You do not have sufficient privileges. Try running the program with 'sudo'.")
24 | exit()
25 | except OSError as e:
26 | if "No such device" in str(e):
27 | self.console.print(f"[bold red]Error:[/] Interface '{iface}' does not exist. \nPlease provide a valid interface name.")
28 | exit()
29 | else:
30 | raise
31 |
32 | devices = []
33 | for sent, received in result:
34 | mac_address = received.hwsrc
35 | manufacturer = self.get_device_info(mac_address)
36 | packet_size = len(sent) + len(received)
37 | devices.append({'ip': received.psrc, 'mac': mac_address, 'manufacturer': manufacturer, 'packet_size': packet_size})
38 |
39 | return devices
40 |
41 | def get_device_info(self, mac_address):
42 | try:
43 | url = f"https://api.macvendors.com/{mac_address}"
44 | response = requests.get(url)
45 | if response.status_code == 200:
46 | manufacturer = response.text.strip()
47 | else:
48 | manufacturer = "Unknown"
49 | except requests.exceptions.RequestException:
50 | manufacturer = "Unknown"
51 |
52 | return manufacturer
53 |
54 | def apply_filters(self, devices, manufacturer_filter, ip_range_filter):
55 | filtered_devices = []
56 | for device in devices:
57 | if (not manufacturer_filter or device['manufacturer'].lower() == manufacturer_filter.lower()) and (not ip_range_filter or self.ip_in_range(device['ip'], ip_range_filter)):
58 | filtered_devices.append(device)
59 | return filtered_devices
60 |
61 | def ip_in_range(self, ip, ip_range):
62 | try:
63 | ip_network = ip_range.split('/')[0]
64 | subnet_mask = int(ip_range.split('/')[1])
65 | return ipaddress.ip_address(ip) in ipaddress.ip_network(ip_network, strict=False)
66 | except ValueError:
67 | return False
68 |
69 | def display_live_updates(self, targets, interfaces, output_file=None, manufacturer_filter=None, ip_range_filter=None, scan_rate=5):
70 | known_devices = []
71 | while True:
72 | for target in targets:
73 | for iface in interfaces:
74 | devices = self.scan(target, iface)
75 |
76 | # Save newly discovered devices
77 | new_devices = [d for d in devices if d not in known_devices]
78 | if new_devices and output_file:
79 | self.save_results(new_devices, output_file)
80 | known_devices.extend(new_devices)
81 |
82 | filtered_devices = self.apply_filters(devices, manufacturer_filter, ip_range_filter)
83 |
84 | self.console.clear()
85 | table = Table(show_header=True, header_style="bold magenta")
86 | table.add_column("IP Address", style="bold red")
87 | table.add_column("MAC Address", style="bold blue")
88 | table.add_column("Packet Size", style="bold green")
89 | table.add_column("Manufacturer", style="bold yellow")
90 |
91 | for device in filtered_devices:
92 | table.add_row(device['ip'], device['mac'], str(device['packet_size']), device['manufacturer'])
93 |
94 | self.console.print(table)
95 | time.sleep(scan_rate)
96 |
97 | def save_results(self, devices, output_file):
98 | with open(output_file, 'a') as f: # 'a' for append mode
99 | for device in devices:
100 | f.write(f"IP Address: {device['ip']}\n")
101 | f.write(f"MAC Address: {device['mac']}\n")
102 | f.write(f"Manufacturer: {device['manufacturer']}\n")
103 | f.write(f"Packet Size: {device['packet_size']} bytes\n")
104 | f.write("\n")
105 |
106 | def main(self):
107 | try:
108 | args = self.get_arguments()
109 | targets = args.target
110 | interfaces = args.interface
111 | live_tracking = args.live
112 | output_file = args.output
113 | manufacturer_filter = args.manufacturer
114 | ip_range_filter = args.ip_range
115 | scan_rate = args.scan_rate
116 |
117 | devices = []
118 | for target in targets:
119 | for iface in interfaces:
120 | devices.extend(self.scan(target, iface))
121 |
122 | table = Table(show_header=True, header_style="bold magenta")
123 | table.add_column("IP Address", style="bold red")
124 | table.add_column("MAC Address", style="bold blue")
125 | table.add_column("Packet Size", style="bold green")
126 | table.add_column("Manufacturer", style="bold yellow")
127 |
128 | filtered_devices = self.apply_filters(devices, manufacturer_filter, ip_range_filter)
129 |
130 | for device in filtered_devices:
131 | table.add_row(device['ip'], device['mac'], str(device['packet_size']), device['manufacturer'])
132 |
133 | self.console.print(table)
134 |
135 | if output_file:
136 | self.save_results(filtered_devices, output_file)
137 | self.console.print(f"\n[bold green]Results saved to: {output_file}")
138 |
139 | if live_tracking:
140 | t = threading.Thread(target=self.display_live_updates, args=(targets, interfaces, output_file, manufacturer_filter, ip_range_filter, scan_rate))
141 | t.daemon = True # This makes sure the thread will exit when the main program exits
142 | t.start()
143 | while t.is_alive(): # This loop will keep the main thread running while the display thread is running
144 | time.sleep(1)
145 | except KeyboardInterrupt:
146 | self.console.print("\n[bold red]Program interrupted by user. Exiting...")
147 |
148 | def get_arguments(self):
149 | parser = argparse.ArgumentParser(description='NetProbe: Network Scanner Tool')
150 | parser.add_argument("-t", "--target", metavar="", help="Target IP address or subnet (default: 192.168.1.0/24)", default="192.168.1.0/24", nargs='+', required=True)
151 | parser.add_argument("-i", "--interface", metavar="", help="Interface to use (default: None)", default=None, nargs='+', required=True)
152 | parser.add_argument("-l", "--live", action="store_true", help="Enable live tracking of devices")
153 | parser.add_argument("-o", "--output", metavar="", help="Output file to save the results")
154 | parser.add_argument("-m", "--manufacturer", metavar="", help="Filter by manufacturer (e.g., 'Apple')")
155 | parser.add_argument("-r", "--ip-range", metavar="", help="Filter by IP range (e.g., '192.168.1.0/24')")
156 | parser.add_argument("-s", "--scan-rate", metavar="", help="Scan rate in seconds (default: 5)", type=int, default=5)
157 | return parser.parse_args()
158 |
159 |
160 | if __name__ == "__main__":
161 | net_probe = NetProbe()
162 | net_probe.main()
163 |
--------------------------------------------------------------------------------