├── .gitignore ├── src ├── core │ ├── mod.rs │ └── scan_ip.rs └── main.rs ├── Cargo.lock ├── Cargo.toml ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod scan_ip; -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "portsniffer" 7 | version = "1.0.2" 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "portsniffer" 3 | version = "1.0.2" 4 | author = "Vin H" 5 | license = "MIT" 6 | description = "A simple port sniffer written in Rust" 7 | repository = "https://github.com/vin-ll/port_sniffer_cli" 8 | homepage = "https://github.com/vin-ll/port_sniffer_cli" 9 | documentation = "https://github.com/vin-ll/port_sniffer_cli" 10 | readme = "README.md" 11 | keywords = ["port", "sniffer", "rust"] 12 | categories = ["networking"] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | rayon = "1.8.0" 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Port Sniffer CLI 2 | Simple port sniffer made in rust as a learning project. 3 | Please only use this for ethical purposes only! 4 | 5 | # Usage 6 | ## Install via cargo 7 | To install run: 8 | ```bash 9 | cargo install portsniffer 10 | ``` 11 | To start portsniffer run: 12 | ```bash 13 | portsniffer -h 14 | ``` 15 | 16 | 17 | ## Build the project 18 | To build it run: 19 | ```bash 20 | cargo build --release 21 | ``` 22 | To start it run: 23 | #### Linux: 24 | ```bash 25 | ./release/debug/portsniffer -s IPV4ADDRESS 26 | ``` 27 | #### Windows: 28 | Open the folder target/debug and then execute portsniffer.exe 29 | 30 | ### To gather help run 31 | ```bash 32 | ./target/debug/portsniffer -h 33 | ``` 34 | or (if installed via cargo) 35 | ```bash 36 | portsniffer -h 37 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Vin H 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. -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod core; 2 | 3 | fn print_usage(program_name: &str) { 4 | println!("Usage: {} [command] [arguments]", program_name); 5 | println!(" -h, --help\tDisplay this help message"); 6 | println!(" -v, --version\tDisplay the version of this program"); 7 | println!(" -s, --scan\tScan an IP address. Usage: -s [IPv4 Address]"); 8 | println!(" -p, --ports\tSpecify ports to scan. Usage: -p [PORT1,PORT2,...]"); 9 | } 10 | 11 | fn main() { 12 | let args: Vec = std::env::args().collect(); 13 | 14 | if args.len() == 1 { 15 | eprintln!( 16 | "Error: You need to specify a command to run. Run -h or --help for more information." 17 | ); 18 | std::process::exit(1); 19 | } 20 | 21 | match args[1].as_str() { 22 | "-h" | "--help" => print_usage(&args[0]), 23 | "-v" | "--version" => println!("Version: {}", env!("CARGO_PKG_VERSION")), 24 | "-s" | "--scan" => { 25 | if args.len() == 2 { 26 | print_usage(&args[0]); 27 | std::process::exit(1); 28 | } 29 | 30 | let ip = &args[2]; 31 | let mut ports: Option> = None; 32 | 33 | // Check for ports parameter 34 | if args.len() > 4 && (args[3] == "-p" || args[3] == "--ports") { 35 | ports = Some(parse_ports(&args[4])); 36 | } 37 | 38 | core::scan_ip::scan(ip, ports); 39 | } 40 | _ => { 41 | print_usage(&args[0]); 42 | std::process::exit(1); 43 | } 44 | } 45 | } 46 | 47 | fn parse_ports(ports_str: &str) -> Vec { 48 | ports_str 49 | .split(',') 50 | .filter_map(|p| p.trim().parse::().ok()) 51 | .collect() 52 | } 53 | -------------------------------------------------------------------------------- /src/core/scan_ip.rs: -------------------------------------------------------------------------------- 1 | use std::net::TcpStream; 2 | use std::sync::{ Arc, Mutex }; 3 | use std::thread; 4 | use std::time::{ Duration, Instant }; 5 | use std::io::Write; 6 | 7 | pub fn scan(ip: &str, ports_to_scan: Option>) { 8 | let ip = ip.to_string(); 9 | let open_ports = Arc::new(Mutex::new(Vec::new())); 10 | 11 | // specified ports or all ports (default) 12 | // 13 | // if ports_to_scan is None, scan all ports from 1 to 65535 14 | // if ports_to_scan is Some, scan only the specified ports 15 | let ports_vec = ports_to_scan.clone().unwrap_or((1..65535).collect::>()); 16 | let total_ports = ports_vec.len(); 17 | let completed = Arc::new(Mutex::new(0)); 18 | let start_time = Instant::now(); 19 | 20 | // set up worker pool - use number of CPUs for optimal performance 21 | let num_threads = thread 22 | ::available_parallelism() 23 | .map(|p| p.get()) 24 | .unwrap_or(8); 25 | 26 | if let Some(port_list) = ports_to_scan { 27 | println!( 28 | "Port sniffer CLI | Starting scan of {} with {} threads | Scanning {} specific ports", 29 | ip, 30 | num_threads, 31 | port_list.len() 32 | ); 33 | } else { 34 | println!("Port sniffer CLI | Starting scan of {} with {} threads", ip, num_threads); 35 | } 36 | 37 | rayon::ThreadPoolBuilder 38 | ::new() 39 | .num_threads(num_threads) 40 | .build() 41 | .unwrap() 42 | .scope(|s| { 43 | let batch_size = 1000; 44 | let port_range: Vec> = ports_vec 45 | .chunks(batch_size) 46 | .map(|chunk| chunk.to_vec()) 47 | .collect(); 48 | 49 | for batch in port_range { 50 | let ip = ip.clone(); 51 | let open_ports = Arc::clone(&open_ports); 52 | let completed = Arc::clone(&completed); 53 | let start_time = start_time; 54 | 55 | s.spawn(move |_| { 56 | let mut batch_open_ports = Vec::new(); 57 | 58 | for port in batch { 59 | let addr = format!("{}:{}", ip, port); 60 | match 61 | TcpStream::connect_timeout( 62 | &addr.parse().unwrap(), 63 | Duration::from_millis(500) 64 | ) 65 | { 66 | Ok(_) => batch_open_ports.push(port), 67 | Err(_) => {} 68 | } 69 | 70 | let mut completed_count = completed.lock().unwrap(); 71 | *completed_count += 1; 72 | 73 | if *completed_count % 100 == 0 || *completed_count == total_ports { 74 | let percentage = 75 | ((*completed_count as f64) / (total_ports as f64)) * 100.0; 76 | let elapsed_time = start_time.elapsed(); 77 | let time_per_port = 78 | elapsed_time.as_secs_f64() / (*completed_count as f64); 79 | let remaining_ports = total_ports - *completed_count; 80 | let remaining_time_secs = (time_per_port * 81 | (remaining_ports as f64)) as u64; 82 | 83 | // carriage return instead of clearing screen for smoother updates 84 | print!( 85 | "\r{:.1}% complete. ETA: {}m {}s | Ports checked: {} ", 86 | percentage, 87 | remaining_time_secs / 60, 88 | remaining_time_secs % 60, 89 | *completed_count 90 | ); 91 | let _ = std::io::stdout().flush(); 92 | } 93 | } 94 | 95 | if !batch_open_ports.is_empty() { 96 | let mut all_open_ports = open_ports.lock().unwrap(); 97 | all_open_ports.extend(batch_open_ports); 98 | } 99 | }); 100 | } 101 | }); 102 | 103 | // end of scope, all threads will be joined here 104 | let mut final_open_ports = open_ports.lock().unwrap().clone(); 105 | final_open_ports.sort(); 106 | 107 | let elapsed_time = start_time.elapsed(); 108 | println!( 109 | "\nScan completed in {}m {}s", 110 | elapsed_time.as_secs() / 60, 111 | elapsed_time.as_secs() % 60 112 | ); 113 | 114 | if final_open_ports.is_empty() { 115 | println!("No open ports found."); 116 | } else { 117 | println!("STATE\tPORT\tPROTOCOL"); 118 | for port in &final_open_ports { 119 | println!("OPEN\t{}\tTCP", port); 120 | } 121 | println!("\nFound {} open ports", final_open_ports.len()); 122 | } 123 | } 124 | --------------------------------------------------------------------------------