# MIT License

# Copyright (c) 2017 Balazs Bucsay

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import sys

if "ICMP.py" in sys.argv[0]:
	print("[-] Instead of poking around just try: python xfltreat.py --help")
	sys.exit(-1)

import socket
import time
import select
import os
import struct
import threading
import random
import subprocess

#local files
import Stateless_module
import encryption
import client
import common
from support.icmp_proto import ICMP_Proto
from support.icmp_proto import ICMP_Client

class ICMP(Stateless_module.Stateless_module):

	module_name = "ICMP"
	module_configname = "ICMP"
	module_description = """ICMP type 8+0 module. Sends ping requests and 
	responses. Just an ordinary ping tunnel."""
	module_os_support = common.OS_LINUX | common.OS_MACOSX | common.OS_WINDOWS | common.OS_FREEBSD

	def __init__(self):
		super(ICMP, self).__init__()
		self.icmp = ICMP_Proto()
		self.ICMP_sequence = 0
		# identifier lottery
		self.ICMP_identifier = int(random.random() * 65535)
		# serverport lottery, not like it matters
		self.ICMP_fake_serverport = int(random.random() * 65535)
		# prefix to make it easier to detect xfl packets
		self.ICMP_prefix = "XFL"
		self.timeout = 2.0
		# if the recv-sent>threshold:
		self.TRACKING_THRESHOLD = 50
		# then we cut back the difference with adjust:
		self.TRACKING_ADJUST = 20

		return

	def init_client(self, control_message, additional_data):
		addr = additional_data[0]
		identifier = additional_data[1]
		sequence = additional_data[2]

		client_local = ICMP_Client()
		client_local.set_ICMP_received_identifier(identifier)
		client_local.set_ICMP_received_sequence(sequence)
		client_local.set_ICMP_sent_identifier(identifier)
		client_local.set_ICMP_sent_sequence(sequence)

		client_private_ip = control_message[0:4]
		client_public_source_ip = socket.inet_aton(addr[0])
		client_public_source_port = addr[1]

		# If this private IP is already used, the server removes that client.
		# For example: client reconnect on connection reset, duplicated configs
		# and yes, this can be used to kick somebody off the tunnel

		# close client related pipes
		for c in self.clients:
			if c.get_private_ip_addr() == client_private_ip:
				save_to_close = c
				self.clients.remove(c)
				if c.get_pipe_r() in self.rlist:
					self.rlist.remove(c.get_pipe_r())

		found = False
		for c in self.packetselector.get_clients():
			if c.get_private_ip_addr() == client_private_ip:
				found = True
				self.packetselector.delete_client(c)

		# If client was created but not added to the PacketSelector, then the
		# pipes still need to be closed. This could happen when the authenti-
		# cation fails or gets interrupted.
		if not found:
			if self.os_type == common.OS_WINDOWS:
				import win32file

				try:
					win32file.CloseHandle(save_to_close.get_pipe_r())
					win32file.CloseHandle(save_to_close.get_pipe_w())
				except:
					pass
			else:
				try:
					save_to_close.get_pipe_r_fd().close()
					save_to_close.get_pipe_w_fd().close()
				except:
					pass

		# creating new pipes for the client
		pipe_r, pipe_w = os.pipe()
		client_local.set_pipes_fdnum(pipe_r, pipe_w)
		client_local.set_pipes_fd(os.fdopen(pipe_r, "r"), os.fdopen(pipe_w, "w"))

		# set connection related things and authenticated to True
		client_local.set_public_ip_addr(client_public_source_ip)
		client_local.set_public_src_port(client_public_source_port)
		client_local.set_private_ip_addr(client_private_ip)

		client_local.get_encryption().set_module(self.encryption.get_module())
		self.encryption = client_local.get_encryption()

		if self.encryption.get_module().get_step_count():
			# add encryption steps
			self.merge_cmh(self.encryption.get_module().get_cmh_struct())

		if self.authentication.get_step_count():
			# add authentication steps
			self.merge_cmh(self.authentication.get_cmh_struct())

		client_local.set_initiated(True)
		self.clients.append(client_local)

		return

	def lookup_client_pub(self, additional_data):
		addr = additional_data[0]
		identifier = additional_data[1]
		client_public_ip = socket.inet_aton(addr[0])

		for c in self.clients:
			if (c.get_public_ip_addr() == client_public_ip) and (c.get_ICMP_received_identifier() == identifier):
				return c

		return None

	def post_authentication_server(self, control_message, additional_data):
		addr = additional_data[0]
		identifier = additional_data[1]
		c = self.lookup_client_pub((addr, identifier))
		if c.get_initiated():
			c.set_authenticated(True)
			self.packetselector.add_client(c)
			if c.get_pipe_r() not in self.rlist:
				self.rlist.append(c.get_pipe_r())
			return True

		return False

	def remove_initiated_client(self, control_message, additional_data):
		addr = additional_data[0]
		identifier = additional_data[1]
		c = self.lookup_client_pub((addr, identifier))
		if c:
			self.packetselector.delete_client(c)
			if c.get_authenticated():
				self.rlist.remove(c.get_pipe_r())
			self.clients.remove(c)

		return

	def communication_initialization(self):
		self.clients = []
		if self.serverorclient:
			if self.os_type == common.OS_LINUX:
				ps = subprocess.Popen(["cat", "/proc/sys/net/ipv4/icmp_echo_ignore_all"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
				(stdout, stderr) = ps.communicate()
				if stderr:
					common.internal_print("Error: deleting default route: {0}".format(stderr), -1)
					sys.exit(-1)
				self.orig_ieia_value = stdout[0:1]
				os.system("echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all")

		if self.serverorclient:
			self.ICMP_send = self.icmp.ICMP_ECHO_RESPONSE
		else:
			self.ICMP_send = self.icmp.ICMP_ECHO_REQUEST
		return

	def modify_additional_data(self, additional_data, serverorclient):
		if serverorclient:
			c = self.lookup_client_pub(additional_data)
			if c:
				c.set_ICMP_sent_sequence(additional_data[2])
			return additional_data
		else:
			# increment sequence in additional data
			self.ICMP_sequence += 1
			return (additional_data[0], additional_data[1], self.ICMP_sequence, additional_data[3])

	# check request: generating a challenge and sending it to the server
	# in case the answer is that is expected, the targer is a valid server
	def do_check(self):
		message, self.check_result = self.checks.check_default_generate_challenge()
		self.send(common.CONTROL_CHANNEL_BYTE, common.CONTROL_CHECK+message, 
			(self.server_tuple, self.ICMP_identifier, 0, 0))

		return

	# start talking to the server
	# do authentication or encryption first
	def do_hello(self):
		# TODO: maybe change this later to push some more info, not just the 
		# private IP
		message = socket.inet_aton(self.config.get("Global", "clientip"))
		self.send(common.CONTROL_CHANNEL_BYTE, common.CONTROL_INIT+message, 
			(self.server_tuple, self.ICMP_identifier, self.ICMP_sequence, 0))

	# Polite signal towards the server to tell that the client is leaving
	# Can be spoofed? if there is no encryption. Who cares?
	def do_logoff(self):
		self.send(common.CONTROL_CHANNEL_BYTE, common.CONTROL_LOGOFF, 
			(self.server_tuple, self.ICMP_identifier, self.ICMP_sequence, 0))

		return

	def do_dummy_packet(self, identifier, sequence):
		self.send(common.CONTROL_CHANNEL_BYTE, common.CONTROL_DUMMY_PACKET, 
			(self.server_tuple, identifier, sequence, 0))

		return

	def send(self, channel_type, message, additional_data):
		addr = additional_data[0]
		identifier = additional_data[1]
		sequence = additional_data[2]
		queue_length = additional_data[3]

		if queue_length < 256:
			ql = chr(queue_length)
		else:
			ql = chr(255)

		if channel_type == common.CONTROL_CHANNEL_BYTE:
			transformed_message = self.transform(self.get_client_encryption(additional_data), ql+common.CONTROL_CHANNEL_BYTE+message, 1)
		else:
			transformed_message = self.transform(self.get_client_encryption(additional_data), ql+common.DATA_CHANNEL_BYTE+message, 1)

		common.internal_print("ICMP sent: {0} seq: {1} id: {2}".format(len(transformed_message), sequence, identifier), 0, self.verbosity, common.DEBUG)

		packet = self.icmp.create_packet(self.ICMP_send, identifier, sequence,
			self.ICMP_prefix+struct.pack(">H", len(transformed_message))+transformed_message)

		# WORKAROUND?!
		# Windows: It looks like when the buffer fills up the OS does not do
		# congestion control, instead throws and exception/returns with
		# WSAEWOULDBLOCK which means that we need to try it again later.
		# So we sleep 100ms and hope that the buffer has more space for us.
		# If it does then it sends the data, otherwise tries it in an infinite
		# loop...
		while True:
			try:
				return self.comms_socket.sendto(packet, addr)
			except socket.error as se:
				if se.args[0] == 10035: # WSAEWOULDBLOCK
					time.sleep(0.1)
					pass
				else:
					raise

	def recv(self):
		message, addr = self.comms_socket.recvfrom(1508)

		identifier = struct.unpack("<H", message[24:26])[0]
		sequence = struct.unpack(">H", message[26:28])[0]

		if message[28:28+len(self.ICMP_prefix)] != self.ICMP_prefix:
			return ("", None, None, None, None)

		message = message[28+len(self.ICMP_prefix):]

		length = struct.unpack(">H", message[0:2])[0]
		if (length+2 != len(message)):
			common.internal_print("Error length mismatch {0} {1}".format(length, len(message)), -1)
			return ("", None, None, None, None)

		message = self.transform(self.get_client_encryption((addr, identifier, 0, 0)), message[2:length+2], 0)
		queue_length = struct.unpack(">B", message[0:1])[0]
		common.internal_print("ICMP read: {0} seq: {1} id: {2}".format(length, sequence, identifier), 0, self.verbosity, common.DEBUG)

		return message[1:], addr, identifier, sequence, queue_length

	def communication_win(self, is_check):
		import win32event
		import win32file
		import win32api
		import pywintypes
		import winerror

		# event for the socket
		hEvent_sock = win32event.CreateEvent(None, 0, 0, None)
		win32file.WSAEventSelect(self.comms_socket, hEvent_sock, win32file.FD_READ)

		# descriptor list
		self.rlist = [self.comms_socket]
		# overlapped list
		self.olist = [0]
		# event list
		self.elist = [hEvent_sock]
		# message buffer list
		self.mlist = [0]
		# id of the read object - put in this if it was read
		self.ulist = []
		if not self.serverorclient and self.tunnel:
				# client mode
				# objects created for the tunnel and put in the corresponding
				# lists
				hEvent_pipe = win32event.CreateEvent(None, 0, 0, None) # for reading from the pipe
				overlapped_pipe = pywintypes.OVERLAPPED()
				overlapped_pipe.hEvent = hEvent_pipe
				message_buffer = win32file.AllocateReadBuffer(4096)
				self.rlist.append(self.tunnel)
				self.olist.append(overlapped_pipe)
				self.elist.append(hEvent_pipe)
				self.mlist.append(message_buffer)
				self.ulist.append(1)

		while not self._stop:
			try:
				if not self.tunnel:
					# check or server mode without client only with socket
					#message, addr = self.comms_socket.recvfrom(1508)
					rc = win32event.WaitForSingleObject(hEvent_sock, int(self.timeout*1000))
					if rc == winerror.WAIT_TIMEOUT:
						# timed out, just rerun and wait
						continue

				else:
					if self.ulist:
						# there is somebody waiting to be read
						for idx in self.ulist:
							# issueing ReadFile on all not yet read mailslots/tunnel
							hr, _ = win32file.ReadFile(self.rlist[idx], self.mlist[idx], self.olist[idx])
							if (hr != 0) and (hr != winerror.ERROR_IO_PENDING):
								common.internal_print("UDP ReadFile failed: {0}".format(hr), -1)
								raise

						self.ulist = []

					# waiting to get some data somewhere
					rc = win32event.WaitForMultipleObjects(self.elist, 0, int(self.timeout*1000))
					if rc == winerror.WAIT_TIMEOUT:
						# timed out, just rerun and wait
						continue

				if rc < 0x80: # STATUS_ABANDONED_WAIT_0
					if rc > 0:
						# the tunnel or one of the mailslots got signalled
						self.ulist.append(rc)
						if (self.olist[rc].InternalHigh < 4) or (self.mlist[rc][0:1] != "\x45"): #Only care about IPv4
							continue

						readytogo = self.mlist[rc][0:self.olist[rc].InternalHigh]

						if self.serverorclient:
							c = self.lookup_client_priv(readytogo)
							if c:
								# if the differece between the received and set sequences too big
								# some routers/firewalls just drop older sequences. If it gets
								# too big, we just drop the older ones and use the latest X packet
								# this helps on stabality.
								if (c.get_ICMP_received_sequence() - c.get_ICMP_sent_sequence()) >= self.TRACKING_THRESHOLD:
									c.set_ICMP_sent_sequence(c.get_ICMP_received_sequence() - self.TRACKING_ADJUST)

								# get client related values: identifier and sequence number
								identifier = c.get_ICMP_sent_identifier()
								sequence = c.get_ICMP_sent_sequence()

								# queueing every packet first
								c.queue_put(readytogo)
								# are there any packets to answer?
								if (c.get_ICMP_received_sequence() - sequence) == 0:
									continue
								else:
									request_num = 0
									# if there is less packet than that we have in the queue
									# then we cap the outgoing packet number
									if (c.get_ICMP_received_sequence() - sequence) < (c.queue_length()):
										number_to_get = (c.get_ICMP_received_sequence() - sequence)
									else:
										# send all packets from the queue
										number_to_get = c.queue_length()

									for i in range(0, number_to_get):
										# get first packet
										readytogo = c.queue_get()
										# is it he last one we are sending now?
										if i == (number_to_get - 1):
											# if the last one and there is more in the queue
											# then we ask for dummy packets
											request_num = c.queue_length()
										# go packets go!
										self.send(common.DATA_CHANNEL_BYTE, readytogo,
											((socket.inet_ntoa(c.get_public_ip_addr()), c.get_public_src_port()),
											identifier, sequence + i + 1, request_num))

									sequence = (sequence + i + 1) % 65536
									c.set_ICMP_sent_sequence(sequence)
							else:
								# there is no client with that IP
								common.internal_print("Client not found, strange?!", 0, self.verbosity, common.DEBUG)
								continue
						else:
							if self.authenticated:
								# whatever we have from the tunnel, just encapsulate it
								# and send it out
								self.ICMP_sequence = (self.ICMP_sequence + 1) % 65536
								self.send(common.DATA_CHANNEL_BYTE, readytogo,
									(self.server_tuple, self.ICMP_identifier, self.ICMP_sequence, 0)) #??
							else:
								common.internal_print("Spoofed packets, strange?!", 0, self.verbosity, common.DEBUG)
								continue
					if rc == 0:
						# socket got signalled
						message, addr, identifier, sequence, queue_length = self.recv()

						if len(message) == 0:
							continue

						c = None
						if self.serverorclient:
							self.authenticated = False
							c = self.lookup_client_pub((addr, 0))
							if c:
								c.set_ICMP_received_identifier(identifier)
								# packets does not arrive in order sometime
								# if higher sequence arrived already, then we
								# do not modify
								# 16bit integer MAX could be a bit tricky, a
								# threshold had to be introduced to make it
								# fail safe. Hacky but should work.
								ICMP_THRESHOLD = 100
								if (sequence > c.get_ICMP_received_sequence()) or ((sequence < ICMP_THRESHOLD) and ((sequence + 65536)>c.get_ICMP_received_sequence()) and (c.get_ICMP_received_sequence()>ICMP_THRESHOLD)):
									c.set_ICMP_received_sequence(sequence)
						else:
							if queue_length:
								common.internal_print("sending {0} dummy packets".format(queue_length), 0, self.verbosity, common.DEBUG)
								for i in range(queue_length+10):
									self.ICMP_sequence = (self.ICMP_sequence + 1) % 65536
									self.do_dummy_packet(self.ICMP_identifier,
										self.ICMP_sequence)

						if common.is_control_channel(message[0:1]):
							if self.controlchannel.handle_control_messages(self, message[len(common.CONTROL_CHANNEL_BYTE):], (addr, identifier, sequence, 0)):
								continue
							else:
								self.stop()
								break

						if c:
							self.authenticated = c.get_authenticated()

						if self.authenticated:
							try:
								self.packet_writer(message[len(common.CONTROL_CHANNEL_BYTE):])
							except OSError as e:
								print(e)

			except win32api.error as e:
				common.internal_print("UDP Exception: {0}".format(e), -1)
			except socket.error as se:
				if se.args[0] == 10054: # port is unreachable
					common.internal_print("Server's port is unreachable: {0}".format(se), -1)
					self._stop = True

		return True


	def communication_unix(self, is_check):
		sequence = 0
		identifier = 0
		self.rlist = [self.comms_socket]
		if not self.serverorclient and self.tunnel:
				self.rlist = [self.tunnel, self.comms_socket]
		wlist = []
		xlist = []

		while not self._stop:
			try:
				readable, writable, exceptional = select.select(self.rlist, wlist, xlist, self.timeout)
			except select.error, e:
				common.internal_print("select.error: %r".format(e), -1)
				break
			try:
				if not readable:
					if is_check:
						raise socket.timeout
					if not self.serverorclient:
						if self.authenticated:
							self.ICMP_sequence = (self.ICMP_sequence + 1) % 65536
							self.do_dummy_packet(self.ICMP_identifier, self.ICMP_sequence)
							common.internal_print("Keep alive sent", 0, self.verbosity, common.DEBUG)
					continue

				for s in readable:
					if (s in self.rlist) and not (s is self.comms_socket):
						message = self.packet_reader(s, True, self.serverorclient)
						while True:
							if (len(message) < 4) or (message[0:1] != "\x45"): #Only care about IPv4
								break
							packetlen = struct.unpack(">H", message[2:4])[0] # IP Total length
							if packetlen > len(message):
								message += self.packet_reader(s, False, self.serverorclient)

							readytogo = message[0:packetlen]
							message = message[packetlen:]
							if self.serverorclient:
								c = self.lookup_client_priv(readytogo)

								if c:
									# if the differece between the received and set sequences too big
									# some routers/firewalls just drop older sequences. If it gets 
									# too big, we just drop the older ones and use the latest X packet
									# this helps on stabality.
									if (c.get_ICMP_received_sequence() - c.get_ICMP_sent_sequence()) >= self.TRACKING_THRESHOLD:
										c.set_ICMP_sent_sequence(c.get_ICMP_received_sequence() - self.TRACKING_ADJUST)

									# get client related values: identifier and sequence number
									identifier = c.get_ICMP_sent_identifier()
									sequence = c.get_ICMP_sent_sequence()

									# queueing every packet first
									c.queue_put(readytogo)
									# are there any packets to answer?
									if (c.get_ICMP_received_sequence() - sequence) == 0:
										continue
									else:
										request_num = 0
										# if there is less packet than that we have in the queue
										# then we cap the outgoing packet number
										if (c.get_ICMP_received_sequence() - sequence) < (c.queue_length()):
											number_to_get = (c.get_ICMP_received_sequence() - sequence)
										else:
											# send all packets from the queue
											number_to_get = c.queue_length()

										for i in range(0, number_to_get):

											# get first packet
											readytogo = c.queue_get()
											# is it he last one we are sending now?
											if i == (number_to_get - 1):
												# if the last one and there is more in the queue
												# then we ask for dummy packets
												request_num = c.queue_length()
											# go packets go!
											self.send(common.DATA_CHANNEL_BYTE, readytogo,
												((socket.inet_ntoa(c.get_public_ip_addr()), c.get_public_src_port()),
												identifier, sequence + i + 1, request_num))

										sequence = (sequence + i + 1) % 65536
										c.set_ICMP_sent_sequence(sequence)
								else:
									# there is no client with that IP
									common.internal_print("Client not found, strange?!", 0, self.verbosity, common.DEBUG)
									continue

							else:
								if self.authenticated:
									# whatever we have from the tunnel, just encapsulate it
									# and send it out
									self.ICMP_sequence = (self.ICMP_sequence + 1) % 65536
									self.send(common.DATA_CHANNEL_BYTE, readytogo,
										(self.server_tuple, self.ICMP_identifier, self.ICMP_sequence, 0)) #??
								else:
									common.internal_print("Spoofed packets, strange?!", 0, self.verbosity, common.DEBUG)
									continue


					if s is self.comms_socket:
						message, addr, identifier, sequence, queue_length = self.recv()
						
						if len(message) == 0:
							continue

						c = None
						if self.serverorclient:
							self.authenticated = False
							c = self.lookup_client_pub((addr, identifier))
							if c:
								c.set_ICMP_received_identifier(identifier)
								# packets does not arrive in order sometime
								# if higher sequence arrived already, then we
								# do not modify
								# 16bit integer MAX could be a bit tricky, a
								# threshold had to be introduced to make it
								# fail safe. Hacky but should work.
								ICMP_THRESHOLD = 100
								if (sequence > c.get_ICMP_received_sequence()) or ((sequence < ICMP_THRESHOLD) and ((sequence + 65536)>c.get_ICMP_received_sequence()) and (c.get_ICMP_received_sequence()>ICMP_THRESHOLD)):
									c.set_ICMP_received_sequence(sequence)
						else:
							if queue_length:
								common.internal_print("sending {0} dummy packets".format(queue_length), 0, self.verbosity, common.DEBUG)
								for i in range(queue_length+10):
									self.ICMP_sequence = (self.ICMP_sequence + 1) % 65536
									self.do_dummy_packet(self.ICMP_identifier,
										self.ICMP_sequence)

						if common.is_control_channel(message[0:1]):
							if self.controlchannel.handle_control_messages(self, message[len(common.CONTROL_CHANNEL_BYTE):], (addr, identifier, sequence, 0)):
								continue
							else:
								self.stop()
								break

						if c:
							self.authenticated = c.get_authenticated()
							
						if self.authenticated:
							try:
								self.packet_writer(message[len(common.CONTROL_CHANNEL_BYTE):])
							except OSError as e:
								print(e)

			except (socket.error, OSError):
				raise
				if self.serverorclient:
					self.comms_socket.close()
				break
			except:
				print("another error")
				raise

		return

	def serve(self):
		server_socket = None
		self.serverorclient = 1

		try:
			common.internal_print("Starting module: {0} on {1}".format(self.get_module_name(), self.config.get("Global", "serverbind")))
		
			server_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
			if (self.os_type == common.OS_WINDOWS) or (self.os_type == common.OS_MACOSX):
				common.internal_print("This module can be run in client mode only on this operating system.", -1)

				self.cleanup()
				return

			self.comms_socket = server_socket
			self.authenticated = False

			self.communication_initialization()
			self.communication(False) 
			
		except KeyboardInterrupt:

				self.cleanup()
				return

		self.cleanup()

		return

	def connect(self):
		try:
			common.internal_print("Starting client: {0}".format(self.get_module_name()))

			server_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
			if self.os_type == common.OS_WINDOWS:
				# this should give back the default route interface IP
				default_host_ip = socket.gethostbyname(socket.gethostname())
				server_socket.bind((default_host_ip, 0))

			self.server_tuple = (self.config.get("Global", "remoteserverip"), self.ICMP_fake_serverport)
			self.comms_socket = server_socket
			self.serverorclient = 0
			self.authenticated = False

			self.communication_initialization()
			self.do_hello()
			self.communication(False)

		except KeyboardInterrupt:
			self.do_logoff()
			self.cleanup()
			raise

		self.cleanup()

		return

	def check(self):
		try:
			common.internal_print("Checking module on server: {0}".format(self.get_module_name()))

			server_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
			if self.os_type == common.OS_WINDOWS:
				# this should give back the default route interface IP
				default_host_ip = socket.gethostbyname(socket.gethostname())
				server_socket.bind((default_host_ip, 0))

			self.server_tuple = (self.config.get("Global", "remoteserverip"), self.ICMP_fake_serverport)
			self.comms_socket = server_socket
			self.serverorclient = 0
			self.authenticated = False
			self.communication_initialization()
			self.do_check()
			self.communication(True)

		except KeyboardInterrupt:
			self.cleanup()
			raise
		except socket.timeout:
			common.internal_print("Checking failed: {0}".format(self.get_module_name()), -1)

		self.cleanup()

		return

	def cleanup(self):
		common.internal_print("Shutting down module: {0}".format(self.get_module_name()))
		if self.serverorclient:
			if self.os_type == common.OS_LINUX:
				os.system("echo {0} > /proc/sys/net/ipv4/icmp_echo_ignore_all".format(self.orig_ieia_value)) #???
		try:
			self.comms_socket.close()
		except:
			pass
		try:
			os.close(self.tunnel)
		except:
			pass