Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit f4f653f7fb31d991d6491dcdd1d66632393eef18
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 12:16:18 2019 -0500

    doc

commit b12d9cba995746c509d0f27754c280f4d5e8ebc9
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 12:15:00 2019 -0500

    benchmark

commit 83cb48272ae80e4c1d7215dcfb34d3e824f778eb
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 12:11:30 2019 -0500

    remove sleep debug FIX'

commit f9c727c23ef3f59a74890048af3bb102c147d5f9
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 12:09:57 2019 -0500

    INWORK: why coro xfail

commit 4c7566c5964fb609dcc684d9c2afa201a6800f81
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 11:36:34 2019 -0500

    legacyok

commit c4f286d6a63cbe01d88572ea9c03966d5f3ffc63
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 11:35:40 2019 -0500

    legacyok

commit 57ab0f2ee726241884f80fba24f93bcb7c8cc001
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 11:09:45 2019 -0500

    default params for coro.main()

commit e951fced5257a23a34659fbec97be834246aeb68
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 10:59:43 2019 -0500

    legacy 1000ms OK

commit a457c6a46a10afea18aa4a8de0092d8e4211ce57
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 10:46:52 2019 -0500

    auto import legacy

commit 6e2718c3e935403d7c8ec2a1a4518cb5afceb48a
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 08:55:35 2019 -0500

    comain OK

commit 12cecf3c921a3f494ab85b27e860bf617753f6e5
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 08:52:25 2019 -0500

    cleanup defaults 0.58 sec OK

commit f9931954288d7b577fdba85b6db9b0ca495be01d
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 08:50:16 2019 -0500

    coro module 0.58 sec OK

commit 33b3a6f2a822297d67f324641060008e80583e8f
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 08:46:56 2019 -0500

    0.58 sec OK

commit 42cc74e459ea5a722817029b3123c1112c437089
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 08:36:38 2019 -0500

    init

commit 6d0095442b2b6afe4d0a539c5ae014f76433ee5b
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 08:33:49 2019 -0500

    0.4 sec OK

commit 295fc4bbd8651653cecccf06b79b1a066b694ac0
Author: Michael Hirsch, Ph.D <[email protected]>
Date:   Sun Feb 17 02:18:15 2019 -0500

    reimplement with asyncio
  • Loading branch information
scivision committed Feb 17, 2019
1 parent 87dfb7a commit 01c0c50
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 186 deletions.
46 changes: 46 additions & 0 deletions FindSSH.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env python
"""
scans IPv4 subnet for SSH servers on Port 22 or other server ports.
Useful for machines that don't/can't have NMAP installed (e.g. Windows),
and device does not have Avahi server.
I wanted to make it as cross-platform as possible,
where the user would have only basic Python installed (Windows)
Michael Hirsch, Ph.D.
Note: timeout value bare minimum is 0.15 seconds for LAN,
suggest using higher values say 0.25 or 0.35.
"""
import ipaddress as ip
from argparse import ArgumentParser
from findssh.base import netfromaddress, getLANip
from findssh import main as comain

PORT = 22


def main():
p = ArgumentParser('scan for hosts with open port, without NMAP')
p.add_argument('-p', '--port', help='single port to try',
default=PORT, type=int)
p.add_argument('-s', '--service', default='',
help='string to match to qualify detections')
p.add_argument('-t', '--timeout', help='timeout to wait for server', type=float)
p.add_argument('-b', '--baseip', help='set a specific subnet to scan')
P = p.parse_args()

if not P.baseip:
ownip = getLANip()
print('own address', ownip)
else:
ownip = ip.ip_address(P.baseip)

net = netfromaddress(ownip)
print('searching', net)

comain(net, P.port, P.service, P.timeout)


if __name__ == '__main__':
main()
41 changes: 29 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@
# Find SSH servers (without NMAP)

Platform-independently find SSH servers (or other services with open ports) on an IPv4 subnet, WITHOUT NMAP.
Scans entire IPv4 subnet in less than 1 second using 100 threads via Python standard library
[concurrent.futures](https://docs.python.org/3/library/concurrent.futures.html).
Scan entire IPv4 subnet in less than 1 second using Python standard library `asyncio` coroutines and a single thread.

The `asyncio` coroutine method uses ONE thread and is significantly *faster* than `concurrent.futures.ThreadPoolExecutor`, even (perhaps especially) with hundreds of threads in the ThreadPool.

Although speed advantages weren't seen in our testing, `findssh` works with PyPy as well.

Matlab `findssh.m` works similarly.

## Install

Just run `findssh.py` directly.
Just run `FindSSH.py` directly.
To allow use from other programs, you can install by:

pip install findssh
Expand All @@ -30,19 +31,35 @@ or from this repo:

## Usage

findssh

or from within Python

```python
import findssh

findssh.run()
from command line:
```sh
findssh
```


### Command line options

* `-s` check the string from the server to attempt to verify the correct service has been found
* `-t` timeout per server (seconds) useful for high latency connection
* `-b` baseip (check other subnet besides your own)
* `-b` baseip (check other subnet besides your own)
* `-p` network port to scan (default 22)

## Benchmark

These tests used 500 ms timeout on WiFi.

Coroutine (single thread, fast, lean, recommended):

```ipython
%timeit findssh.main()
522 ms ± 1.26 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
```

Thread pool (100 thread max, slow, heavy):

```ipython
%timeit findssh.threadpool.main()
1.39 s ± 213 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
```
171 changes: 0 additions & 171 deletions findssh.py

This file was deleted.

1 change: 1 addition & 0 deletions findssh/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .coro import main # noqa: F401
53 changes: 53 additions & 0 deletions findssh/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import ipaddress as ip
import socket

PORT = 22
TIMEOUT = 0.5 # should be at least 0.3 to allow for network / CPU delays.


def getLANip() -> ip.IPv4Address:
""" get IP of own interface
ref: http://stackoverflow.com/a/23822431
"""
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
# don't use link local here (169.254.x.x) unless you have a specific need
try:
s.connect(('<broadcast>', 0))
except OSError:
s.connect(('8.8.8.8', 80)) # for BSD/Mac

name = s.getsockname()[0]

return ip.ip_address(name)


def validateservice(service: str, h: str, b: bytes) -> bool:
if not b: # empty reply
return False
# %% non-empty reply
"""
splitlines is in case the ASCII/UTF8 response is less than 32 bytes,
hoping server sends a \r\n
"""
u = b.splitlines()[0].decode('utf-8', 'ignore')
print('\n', u)


# %% optional service validation
val = True
if service and service not in u.lower():
val = False

return val


def netfromaddress(addr: ip.IPv4Address) -> ip.IPv4Network:

assert isinstance(addr, ip.IPv4Address)

net = ip.ip_network(addr.exploded.rsplit('.', 1)[0]+'.0/24')

assert isinstance(net, ip.IPv4Network)

return net
Loading

0 comments on commit 01c0c50

Please sign in to comment.