Browse Source

Initial commit.

Cyril Roelandt 8 years ago
commit
20ed199d9e

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+*.egg-info
+.tox
+build/*
+dist/*

+ 27 - 0
LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2016, Fédération FDN
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 20 - 0
setup.py

@@ -0,0 +1,20 @@
+from setuptools import setup, find_packages
+
+setup(
+    name = 'vpneverywhere',
+    version = '0.1',
+    url = 'https://code.ffdn.org/Steap/vpn-everywhere',
+    packages = find_packages(),
+    entry_points = {
+        'console_scripts': ['vpneverywhere-cli = vpneverywhere.ui.cli.vpneverywhere_cli:main'],
+    },
+    classifiers = [
+        'Environment :: Console',
+        'Intended Audience :: End Users/Desktop',
+        'License :: OSI Approved :: BSD License',
+        'Operating System :: POSIX :: Linux',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.4'
+        'Programming Language :: Python :: 3.5'
+    ]
+)

+ 3 - 0
tox.ini

@@ -0,0 +1,3 @@
+[testenv:pep8]
+deps=flake8
+commands=flake8 {posargs} vpneverywhere/

+ 0 - 0
vpneverywhere/__init__.py


+ 116 - 0
vpneverywhere/port_scanner.py

@@ -0,0 +1,116 @@
+import queue
+import socket
+import threading
+
+
+TIMEOUT = 1
+MAX_THREADS = 64
+
+
+class Scanner(object):
+    def __init__(self, host):
+        self.host = host
+
+        # The job queue contains tuples of the form (protocol, port). We might
+        # be able to remove this queue by using a lock in _get_next_port(), but
+        # that might not be as optimized as accessing a Queue. Since we are too
+        # lazy to write and run a benchmark, let's just do this.
+        self.job_queue = queue.Queue()
+        for job in self._get_next_port():
+            self.job_queue.put(job)
+        self.opened_ports_queue = queue.Queue()
+
+    def _get_next_port(self):
+        """Return the next (protocol, port) tuple to test."""
+        # TODO: Improve this function :)
+        # TODO: There are probably more "common" ports used for VPNs.
+        self.common_ports = [80, 443, 8080]
+
+        for port in self.common_ports:
+            yield ('udp', port)
+
+        for port in self.common_ports:
+            yield ('tcp', port)
+
+        # TODO: if necessary, check all possible ports
+
+    def _compute_n_threads(self):
+        """Compute the number of threads that can be used to scan ports.
+
+        This is not as trivial as it seems: we need as much parallelism as
+        possible, but we may not be able to open too many connections at once
+        on some networks. Also, we do not want to overload the machine.
+        """
+        # TODO: Replace this stub by a real function.
+        return 4
+
+    def scan(self, n_threads=0):
+        if n_threads <= 0 or n_threads > MAX_THREADS:
+            n_threads = self._compute_n_threads()
+
+        threads = []
+        for i in range(n_threads):
+            t = threading.Thread(target=self._scan)
+            threads.append(t)
+            t.start()
+
+        self.job_queue.join()
+        for t in threads:
+            t.join()
+
+        # TODO: We could probably start yielding the results while the workers
+        # are scanning the network, but that would make the code a bit more
+        # complex and would be more error-prone. Being a bit slow seems OK for
+        # now :)
+        while True:
+            try:
+                yield self.opened_ports_queue.get(block=False)
+            except queue.Empty:
+                break
+            else:
+                self.opened_ports_queue.task_done()
+
+    def _scan(self):
+        while True:
+            try:
+                protocol, port = self.job_queue.get(timeout=1)
+                if protocol == 'udp':
+                    opened = self._scan_udp(port)
+                elif protocol == 'tcp':
+                    opened = self._scan_tcp(port)
+                else:
+                    raise ValueError('Unknown protocol %s' % protocol)
+
+                if opened:
+                    self.opened_ports_queue.put((protocol, port))
+
+                self.job_queue.task_done()
+            except queue.Empty:
+                break
+
+    def _scan_tcp(self, port):
+        """Return True if PORT is opened in TCP."""
+        try:
+            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            s.connect((self.host, port))
+            s.settimeout(TIMEOUT)
+            s.send(b'x')
+            s.recvfrom(1)
+        except (ConnectionRefusedError, socket.timeout):
+            return False
+        finally:
+            s.close()
+
+        return True
+
+    def _scan_udp(self, port):
+        """Return True if PORT is opened in UDP."""
+        try:
+            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+            s.settimeout(TIMEOUT)
+            s.sendto(b'x', (self.host, port))
+            s.recvfrom(1)
+        except socket.timeout:
+            return False
+
+        return True

+ 0 - 0
vpneverywhere/ui/__init__.py


+ 0 - 0
vpneverywhere/ui/cli/__init__.py


+ 20 - 0
vpneverywhere/ui/cli/vpneverywhere_cli.py

@@ -0,0 +1,20 @@
+#!/usr/bin/env/python3
+import os
+import sys
+
+from vpneverywhere.port_scanner import Scanner
+
+
+def main():
+    if len(sys.argv) != 2:
+        print('Usage: %s HOST' % os.path.basename(sys.argv[0]))
+        sys.exit(1)
+
+    host = sys.argv[1]
+    scanner = Scanner(host)
+    for (protocol, port) in scanner.scan(n_threads=8):
+        print('You could try: %s %5d' % (protocol.upper(), port))
+
+
+if __name__ == '__main__':
+    main()