class Analysis(object): """Provide functions to read, analyze and generate summary according to the data collection This class is used to analyze sink, clients and border router. """ sink_data = { 'LogTime': [], 'Sequence': [], 'Message': [], 'SentTimestamp': [], 'RcvdTimeStamp': [], 'TimeDiff': [], 'SentPakcets': 0, 'RcvdPackets': 0 } sink_summary = { 'Client IP': [], '(7) Sink Eth': [], '(8) Sink Rcvd': [], 'Packet Loss': [], 'Latency (ms)': [], 'Start': [], 'End': [], 'Running time (s)': [], 'Client sent': [], } router_summary = { 'Client IP': [], '(4) Router wpan0': [], '(5) Router lowpan0': [], '(6) Router eth0': [] } client_summary = { 'Client IP': [], '(1) Client Send': [], '(2) Client lowpan0': [], '(3) Client wpan0': [] } run_time = [ ] # store the begin,end time of client program. this is a list of tuple (client ip, begin_time, # end time) client_info = [ ] # store client connection information, this is a list of tuple (client ip, client port, # sink ip, sink port) router_mac = '' utl = utils.Utils() def __init__(self): """Constructor initializes variables """ def read_sink_data(self, file): """Read a formatted file line by line. The format: Date Time ClientID SequenceNum MessagePld Timestamp ConnInfo :param file: a file name. :type file: str. """ try: with open(file) as s_file: for num, line in enumerate(s_file, 1): words = str(line).split() self.sink_data['LogTime'].append(words[0] + ' ' + words[1]) self.sink_data['Sequence'].append(words[2]) self.sink_data['Message'].append(words[3]) self.sink_data['SentTimestamp'].append(words[4]) self.sink_data['RcvdTimeStamp'].append(words[5]) self.sink_data['TimeDiff'].append( self.time_diff(words[4], words[5])) self.sink_data['RcvdPackets'] = num self.sink_data['SentPackets'] = words[2] if num == 1: # Get the client connection information. info = words[6].split( '|' ) # client_info format: client|ip.port|sink|ip.port client_ip, client_port = info[1].rsplit('.', 1) sink_ip, sink_port = info[3].rsplit('.', 1) if self.client_info.count( (client_ip, client_port, sink_ip, sink_port)) == 0: self.client_info.append( (client_ip, client_port, sink_ip, sink_port)) except IOError: print('ERROR: cannot read the file: \"' + file + '\".') return {} def get_sink_data(self): """Get the data stored in the data storage. """ return self.sink_data def time_diff(self, time_old, time_new): """Return (absolute) time difference, in milliseconds, between two timestamp. :param time_old: a timestamp in format 'HHMMSSXXX'. :param time_old: a timestamp in format 'HHMMSSXXX'. :type time_old: str. :type time_new: str. """ begin = datetime.strptime(time_old, '%H%M%S%f') after = datetime.strptime(time_new, '%H%M%S%f') if begin > after: temp = begin begin = after after = temp difference = after - begin return difference.microseconds / 1000 def get_latency(self): """Return the average latency (in milliseconds) of the received packets based on the dataset from log file. :return: """ time_diff = 0 for item in self.sink_data['TimeDiff']: time_diff += int(item) return float(time_diff) / self.sink_data['RcvdPackets'] def clear_sink_alydata(self): """Reset dataset and other stateful parameters for sink log analysis. """ self.sink_data = { 'LogTime': [], 'Sequence': [], 'Message': [], 'SentTimestamp': [], 'RcvdTimeStamp': [], 'TimeDiff': [], 'SentPakcets': 0, 'RcvdPackets': 0 } def analyze_sink(self, sink_info): """This function analysis sink server logs and output a summary of logs. :param sink_info: list of sink server information, including [ip, port, interface, log_path]. :type sink_info: list Return: A summary of analyzed data. The returned values are in a dictionary. """ # analysis = Analysis() server_ip = sink_info[0] server_port = sink_info[1] interface = sink_info[2] log_path = sink_info[3] cur_path = os.path.abspath(os.path.curdir) os.chdir(log_path) # Analyze logs in exp directory which contains the application layer logs from clients to sink server. for root, dirs, filenames in os.walk('exp'): for filename in filenames: os.chdir(root) self.read_sink_data(filename) data_set = self.get_sink_data() loss_rate = 1 - float(data_set['RcvdPackets']) / int( str(data_set['SentPackets']).lstrip('0')) # print statistic summary print( '=============================================================' ) print('Log file: ' + filename) print('Total sent packets: ' + str(data_set['SentPackets']).lstrip('0')) print('Received packets: ' + str(data_set['RcvdPackets'])) print('Packet Loss: ' + str(loss_rate)) print('Average latency: ' + str(self.get_latency())) start = data_set['LogTime'][0].split() end = data_set['LogTime'][-1].split() begin = start[0] + ' ' + start[1] after = end[0] + ' ' + end[1] begin = datetime.strptime(begin, '%d/%m/%Y %H:%M:%S') after = datetime.strptime(after, '%d/%m/%Y %H:%M:%S') difference = after - begin self.sink_summary['Packet Loss'].append(loss_rate) self.sink_summary['Client sent'].append( int(str(data_set['SentPackets']).lstrip('0'))) self.sink_summary['(8) Sink Rcvd'].append( int(str(data_set['RcvdPackets']))) self.sink_summary['Latency (ms)'].append( float(self.get_latency())) self.sink_summary['Client IP'].append(filename[:-21]) self.sink_summary['Start'].append(start[0] + ' ' + start[1]) self.sink_summary['End'].append(end[0] + ' ' + end[1]) self.sink_summary['Running time (s)'].append( int(difference.days * 86400 + difference.seconds)) self.run_time.append( (filename[:-21], start[0] + ' ' + start[1], end[0] + ' ' + end[1]) ) # add client ip, endtime to list. only takes time w/o date. self.clear_sink_alydata() os.chdir(cur_path + '/logs/sink') # change back to package directory # Analyze logs of Ethernet interface of sink server. for client in self.sink_summary['Client IP']: dst_port = server_port for info in self.client_info: # info = (client_ip, client_port, sink_ip, sink_port) if client == info[0]: dst_port = info[3] break print 'Sink analysis for client IP: ' + str(client) begin_time, end_time = self.get_run_time(client) begin_time = begin_time.replace(':', '\:') end_time = end_time.replace(':', '\:') dup = 0 # multiple presence lines count = 0 self.sink_summary['(7) Sink Eth'].append(count) os.chdir(cur_path) return self.sink_summary def get_run_time(self, ip): """This function finds the running time period for a specific ip. It returns a tuple (begin_time, end_time) corresponds to the given parameter ip. If any of time was not found, empty string '' will be returned. The return time format: %Y-%m-%d %H:%M:%S :param ip: ip address Return: tuple - (begin_time, end_time) """ begin_time = '' end_time = '' for item in self.run_time: if item[0] == ip: begin_time = item[1] # format: DD/MM/YYY HH:MM:SS end_time = item[2] # format: DD/MM/YYYY HH:MM:SS # convert to format: YYYY-MM-DD HH:MM:SS d = datetime.strptime(begin_time, '%d/%m/%Y %H:%M:%S') begin_time = d.strftime('%Y-%m-%d %H:%M:%S') d = datetime.strptime(end_time, '%d/%m/%Y %H:%M:%S') end_time = d.strftime('%Y-%m-%d %H:%M:%S') return begin_time, end_time def ip6_to_mac(self, ip): """This function convert an IPv6 address to the mac address. This function can also convert a link-layer address to mac address. :param ip: an IPv6 address. :type ip: str Return: str - mac address. """ items = ip.rsplit(':', 3) mac = '' if len( items ) == 4: # generate link-layer address and add commands to the script for i in range(1, 4): part = items[i].zfill(4) mac += part[0:2] + ':' + part[2:4] + ':' mac = mac.strip(':') else: raise IOError("IP address: " + ip + " is invalid.") return mac def analyze_router(self, logdir, client_info=None): """This function analyzes border router logs and output a summary of data. :param client_info: information to be used in the analysis, including a list of tuple (client_ip, client_port, sink_ip, sink_port) :param logdir: path to the router log directory. :type client_info: list :type logdir: str Return: dict - a summary of logs. """ if client_info is None: client_info = self.client_info if not os.path.exists(logdir): raise IOError("Log directory: " + logdir + " does not exist.") # Get the router mac from router.info cwd = os.getcwd() os.chdir(logdir) with open('router.info', 'r') as router_file: started = False for (num, line) in enumerate(router_file, 1): if str(line).find( 'lowpan0') != -1: # lowpan information started. started = True if started: if str(line).find( 'Scope:Link' ) != -1: # found the line contain lladdr. pos_begin = str(line).find( 'fe80::') # begin position of lladdr. pos_end = str(line).find( '/', pos_begin) # end position of lladdr. self.router_mac = self.ip6_to_mac( line[pos_begin:pos_end]) break print('Preparing log files.') subprocess.call( "sudo tcpdump -r wpan0.log -ttttnnvvS > read_wpan0.log", shell=True) # Analyze router logs for every client for client in client_info: client_ip = client[0] client_port = client[1] sink_ip = client[2] sink_port = client[3] self.router_summary['Client IP'].append(client_ip) print 'Analyzing router logs for Client IP: ' + str(client_ip) # Analyze lowpan0 log begin_time, end_time = self.get_run_time(client_ip) begin_time = begin_time.replace(':', '\:') end_time = end_time.replace(':', '\:') dup = 0 # multiple presence lines count = 0 if begin_time != '' and end_time != '': # begin/end time presented, count more precisely. proc = subprocess.Popen( "sudo tcpdump -r lowpan0.log -ttttnnvvS dst " + sink_ip + " and dst port " + sink_port + " and src " + client_ip + " | grep -c -e \'" + end_time.replace('\:', ':') + "'", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = proc.communicate() dup = int(out.strip()) proc = subprocess.Popen( "sudo tcpdump -r lowpan0.log -ttttnnvvS dst " + sink_ip + " and dst port " + sink_port + " and src " + client_ip + " | sed -n -e '/" + begin_time + "/,/" + end_time + "/p\' | wc -l", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = proc.communicate() count = int(out.strip()) if count == 0: # begin/end time does not present or not record found, trying to count as usual proc = subprocess.Popen( "sudo tcpdump -r lowpan0.log -ttttnnvvS dst " + sink_ip + " and dst port " + sink_port + " and src " + client_ip + " | wc -l", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = proc.communicate() count = int(out.strip()) if dup > 1: # there are multiple lines with the same end_time point. count = count + dup - 1 self.router_summary['(5) Router lowpan0'].append(count) # Analyze eth0 log begin_time, end_time = self.get_run_time(client_ip) begin_time = begin_time.replace(':', '\:') end_time = end_time.replace(':', '\:') dup = 0 # multiple presense lines count = 0 if begin_time != '' and end_time != '': # begin/end time presented, count more precisely. proc = subprocess.Popen( "sudo tcpdump -r eth0.log -ttttnnvvS dst " + sink_ip + " and dst port " + sink_port + " and src " + client_ip + " | grep -c -e \'" + end_time.replace('\:', ':') + "'", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = proc.communicate() dup = int(out.strip()) proc = subprocess.Popen( "sudo tcpdump -r eth0.log -ttttnnvvS dst " + sink_ip + " and dst port " + sink_port + " and src " + client_ip + " | sed -n -e '/" + begin_time + "/,/" + end_time + "/p\' | wc -l", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = proc.communicate() count = int(out.strip()) if count == 0: # begin/end time does not present, count as usual proc = subprocess.Popen( "sudo tcpdump -r eth0.log -ttttnnvvS dst " + sink_ip + " and dst port " + sink_port + " and src " + client_ip + " | wc -l", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = proc.communicate() count = int(out.strip()) if dup > 1: # there are multiple lines with the same end_time point. count = count + dup - 1 self.router_summary['(6) Router eth0'].append(count) # Analyze wpan0 log begin_time, end_time = self.get_run_time(client_ip) begin_time = begin_time.replace(':', '\:') end_time = end_time.replace(':', '\:') dup = 0 # multiple presense lines count = 0 if begin_time != '' and end_time != '': # begin/end time presented, count more precisely. proc = subprocess.Popen( "grep read_wpan0.log -e \'" + self.router_mac + " <\' | grep -e \'" + self.ip6_to_mac(client_ip) + "\' | grep -c -e \'" + end_time.replace('\:', ':') + "'", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = proc.communicate() dup = int(out.strip()) proc = subprocess.Popen("sed -n -e '/" + begin_time + "/,/" + end_time + "/p\' read_wpan0.log | \ grep -e \'" + self.router_mac + " <\' | grep -e \'" + self.ip6_to_mac(client_ip) + "\' | wc -l", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = proc.communicate() count = int(out.strip()) if count == 0: # begin/end time does not present or record not found, trying to count as usual proc = subprocess.Popen("grep read_wpan0.log -e \'" + self.router_mac + " <\' | grep -e \'" + self.ip6_to_mac(client_ip) + "\' | wc -l", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = proc.communicate() count = int(out.strip()) if dup > 1: # there are multiple lines with the same end_time point. count = count + dup - 1 self.router_summary['(4) Router wpan0'].append(count) os.chdir(cwd) return self.router_summary def analyze_client(self, log_path): """This function analyze client logs and output a summary of data. :param log_path: directory of client log files Return: dict - a summary of logs. """ if not os.path.exists(log_path): raise IOError("Log directory: " + log_path + " does not exist.") cur_path = os.path.abspath(os.path.curdir) os.chdir(log_path) log_dirs = [] for root, dirs, files in os.walk('.'): log_dirs = dirs break # Walk though the log directory and analyze data in each subdirectory. for path in log_dirs: os.chdir(path) # get from conn.log for client ip, sink ip and sink port client_ip = '' sink_ip = '' sink_port = '' with open('conn.log', 'r') as c_file: for (num, line) in enumerate(c_file, 1): if str(line).find('Client') != -1: # line for client ip. client_ip = line.strip().split('|')[1] if line.find('Sink') != -1: # line for sink|ip.port sink_ip, sink_port = line.strip().split('|')[1].rsplit( '.', 1) print 'Analyzing logs for Client IP: ' + str(client_ip) # proceed send.log p = subprocess.Popen("wc -l < send.log", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = p.communicate() self.client_summary['(1) Client Send'].append(int(out.strip())) # proceed lowpan0.log begin_time, end_time = self.get_run_time(client_ip) begin_time = begin_time.replace(':', '\:') end_time = end_time.replace(':', '\:') dup = 0 # multiple presence lines count = 0 if begin_time != '' and end_time != '': # begin/end time presented, ount more precisely. p = subprocess.Popen( "sudo tcpdump -r lowpan0.log -ttttnnvvS dst " + sink_ip + " and dst port " + sink_port + " and src " + client_ip + " | grep -c -e \'" + end_time.replace('\:', ':') + "'", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = p.communicate() dup = int(out.strip()) p = subprocess.Popen( "sudo tcpdump -r lowpan0.log -ttttnnvvS dst " + sink_ip + " and dst port " + sink_port + " and src " + client_ip + " | sed -n -e '/" + begin_time + "/,/" + end_time + "/p\' | wc -l", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = p.communicate() count = int(out.strip()) if count == 0: # begin/end time does not present or record not found, trying to count as usual p = subprocess.Popen( "sudo tcpdump -r lowpan0.log -ttttnnvvS dst " + sink_ip + " and dst port " + sink_port + " and src " + client_ip + " | wc -l", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = p.communicate() count = int(out.strip()) if dup > 1: # there are multiple lines with the same end_time point. count = count + dup - 1 self.client_summary['(2) Client lowpan0'].append(count) # proceed wpan0.log begin_time, end_time = self.get_run_time(client_ip) begin_time = begin_time.replace(':', '\:') end_time = end_time.replace(':', '\:') dup = 0 # multiple presense lines count = 0 subprocess.call( 'sudo tcpdump -r wpan0.log -ttttnnvvS > read_wpan0.log', shell=True) if begin_time != '' and end_time != '': # begin/end time presented, count more precisely. p = subprocess.Popen( "grep read_wpan0.log -e \'" + self.router_mac + " <\' | grep -e \'" + self.ip6_to_mac(client_ip) + "\' | grep -c -e \'" + end_time.replace('\:', ':') + "'", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = p.communicate() dup = int(out.strip()) p = subprocess.Popen("sed -n -e '/" + begin_time + "/,/" + end_time + "/p\' read_wpan0.log | \ grep -e \'" + self.router_mac + " <\' | grep -e \'" + self.ip6_to_mac(client_ip) + "\' | wc -l", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = p.communicate() count = int(out.strip()) if count == 0: # begin/end time does not present, count as usual p = subprocess.Popen("grep read_wpan0.log -e \'" + self.router_mac + " <\' | grep -e \'" + self.ip6_to_mac(client_ip) + "\' | wc -l", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (out, err) = p.communicate() count = int(out.strip()) if dup > 1: # there are multiple lines with the same end_time point. count = count + dup - 1 self.client_summary['(3) Client wpan0'].append(count) os.chdir('..') os.chdir(cur_path) return self.client_summary
class SinkClientDTLS(object): """ This is a class to implement a client which can connect and send messages to a sink server via DTLS protected channel. This class needs a configuration file 'client_expcnf' to configure certificate, network and experiment parameters. This class depends on the certificate management class CertManager. """ dw = None seq = 0 utl = utils.Utils() cm = certmngr.CertManager() # for certificate generation config = { 'CAIP': '', 'CAPORT': '', 'CERT': '', 'TIMESERVER': '', 'CACERT': '', 'SK': '', 'SERVERIP': '', 'SERVERPORT': '', 'CACHAIN': '', 'TYPE': '', 'CERT_REQS': 'CERT_REQUIRED', 'TIMEZONE': '', 'SYNCTIME': 0, 'REFLOWPAN': 0, 'SYSWAIT': 0, 'PAYLOADLEN': 0, 'DATE': '', 'SENDTIME': 0, 'SENDRATE': 0, 'DEVNUM': 0 } client_cnf = 'client_expcnf' # the path to configuration file for this client package package_path = '' # the path to this pakcage MAX_LEN = 1024 # the max length of packet which can be sent and received TIMER = 8200 send_logger = None # a log handler for recording the information of sending messages. counter = 0 term = 0 # number of terms per second. For example, term=10 if the sending timer is in 100 milliseconds sent_counter = 0 # the number of times of sent messages. is_first_time = 1 # indicate if it is an initial time synchronization. def __init__(self): self.package_path = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe()))) os.chdir(self.package_path) self.config = self.utl.read_config(self.client_cnf, self.config) self.cm.init_config(config=self.client_cnf) def create_conn_log(self, dst_addr): """This function creates a connection log file which contains local network interface information and destination address. :param dst_addr: destination address (host, port) :type dst_addr: tuple """ if os.path.exists('conn.log'): self.utl.call('sudo rm -f conn.log', shell=True) print 'Old connection log has been removed.' formatter = logging.Formatter(fmt='%(asctime)s\t\n%(message)s', datefmt='%d/%m/%Y %H:%M:%S') conn_logger = self.create_logger('Connection log.', 'conn.log', logging.INFO, formatter) p = subprocess.Popen('/sbin/ifconfig', stdout=subprocess.PIPE, shell=True) (out, err) = p.communicate() if out is None: out = 'No output!' # get local ip for 6LoWPAN tmp_pos = out.find('lowpan0') end_pos = tmp_pos + out[tmp_pos:].find( ' prefixlen 64 scopeid 0x0<global>') begin_pos = out[:end_pos].rfind('inet6 ') host = out[begin_pos:end_pos].strip() print("OUT: " + host) # local_ip, netmask = host.split()[1].split('/') # inet6 addr: ip/mask Scope:Global local_ip = host out = out + '\nClient|' + str(local_ip) out = out + '\nSink|' + str(dst_addr[0]) + '.' + str(dst_addr[1]) tail = '\n' + "#" * 40 + '\n' conn_log = out + tail conn_logger.info(conn_log) def init_config(self, **args): """This function initializes the configuration for the class object, where the parameters are read from a configuration file. This function should be called before other (class member) function call. The acceptable arguments are: config, C, ST, L, O, OU, CN, emailAddress, ECCPARAM, CAIP, CAPORT, CERT, CSR, MSG, SIG, CACERT, SK, SERVERIP, SERVERPORT, CACHAIN, TYPE, CERT_REQS. Specifically, the keyword "config" sets the path to configuration file. If arguments are passed to this function, the specified configuration file will be updated. :param args: dictionary of passed arguments. """ if args.get('config', '') != '': self.client_cnf = os.path.expanduser(args['config']) if not os.path.isfile(self.client_cnf): raise IOError('configuration file \"' + self.client_cnf + '\" doesn\'t exist or it is not a file.') self.config = self.utl.read_config(self.client_cnf, self.config) # Set run-time configuration which will overwrite the settings in configuration file. update = 0 for key, value in self.config.iteritems(): if args.get(key, '') != '' and args[key] is not None: self.config[key] = args[key] update += 1 if update > 0: self.utl.update_config(self.client_cnf, self.config) # Backup configuration file self.utl.call('cp -f ' + self.client_cnf + ' ' + self.config['CN'] + '.clientcnf.bck', shell=True) def install_dependencies(self): """Install some dependencies. """ self.utl.call('sudo apt-get -y --force-yes install ntp ntpdate', shell=True) self.utl.call('sudo apt-get -y --force-yes install tcpdump', shell=True) self.utl.call('sudo apt-get -y --force-yes install sshpass', shell=True) def create_logger(self, log_name, logfile, level, formatter=None): """Create and return a reference to a logoer. This is used to create difference log files in different settings. :param log_name: the name of a log. :param logfile: the file name of the log. :param level: the log level. :param formatter: the format of log items. :type log_name: str. :type logfile: str. :type level: int. :type formatter: logging.Formatter """ logger = logging.getLogger(log_name) logger.setLevel(level) handler = logging.FileHandler(logfile) handler.setFormatter(formatter) logger.addHandler(handler) return logger def signal_handler(self, signum, frame): """This function handles message sending task. """ # send data in some probability (decided from configuration file) per each sending request. # by default, every 100 milliseconds, it tries to send a message. packet_per_sec = float(self.config['SENDRATE']) / float( self.config['DEVNUM']) probability = int(packet_per_sec / (1.0 / float(self.config['SENDTIME'])) * 100) coin = random.randint(0, 99) if coin in range( 0, probability ): # coin is in {0,1,...,probability}, then send a message, otherwise, ignore. timestamp = datetime.now().strftime('%H%M%S%f')[:-3] self.seq += 1 # data = str(self.seq).zfill(8) + '-' + timestamp # create payload seq_str = str(self.seq).zfill(8) other_len = int( self.config['PAYLOADLEN']) - len(seq_str) - len(timestamp) if other_len < 0: other_len = 0 data = seq_str + '-' * other_len + timestamp if data == '' or data is None: self.send_logger.info( 'DATA ERRORS: the generated date is empty or None.') sent = self.dw.sendto(data) if sent == 0: self.send_logger.info( 'SENDING ERRORS: sent message length is ' + str(sent) + ' bytes.') raise RuntimeError('Socket connection broken.') else: self.send_logger.info('Sent message length: ' + str(sent) + ' | Message content: ' + data) def refresh(self, timer, cmd, logger): """Refresh net information periodically. If the parameter num is a negative number, the function will execute in endless mode. :param timer: a timer (in seconds) which specifies a period. :param cmd: the scheduled command to execute. :param logger: a handler to record log into a file. :type timer: long :type cmd: str. :type logger: Logger object. """ try: while True: i = 0 while i < 30: p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) (out, err) = p.communicate() if out is None: out = 'No output!' if err is None: err = 'None.' logger.info(out + '\nError (if any): ' + err + '\n=======================') if out.find('No response.') == -1: break else: i += 1 time.sleep(float(timer)) except KeyboardInterrupt: return def sync_time(self, timezone, server_addr, timer, logger): """Synchronize time with a specified server. :param timezone: local timezone. :param server_addr: server IP address. :param timer: a periodical time to do the time synchronization. :param logger: a handler to record log into a file. :type timezone: str. :type server_addr: str. :type timer: float :type logger: Logger object. """ try: while True: print('Time synchronizing...') if self.is_first_time == 1: self.utl.call('sudo date --set=\'' + self.config['DATE'] + '\'', shell=True) self.is_first_time = 0 if not os.path.exists('/usr/share/zoneinfo/' + timezone): print('Error: timezone is invalid.') return self.utl.call('timedatectl set-timezone ' + timezone, shell=True) p = subprocess.Popen('sudo ntpdate -u ' + server_addr, stdout=subprocess.PIPE, shell=True) (out, err) = p.communicate() if err is None: err = 'None.' logger.info(out + '\nError (if any): ' + err + '\n=======================') print('Time synchronization finished.') time.sleep(timer) except KeyboardInterrupt: return def run_tcpdump(self, tcpdump_cmd): """Run tcpdump by using the given command. :param tcpdump_cmd: a command to run tcpdump. :type tcpdump_cmd: str. """ try: subprocess.check_call(tcpdump_cmd, shell=True) except KeyboardInterrupt: return except: raise def get_scope_id_inet6(self, addr): """This function returns the scope id of a given lladdr if the address exists. :param addr: IPv6 address :return: int. scope id of interface. """ scope_id = -1 try: full_addr = self.expand_address_inet6(addr) key = full_addr.replace(':', '') with open('/proc/net/if_inet6', 'r') as ifaces: for line in ifaces: if line.lower().find( key.lower()) != -1: # found the interface scope_id = int(line.split()[1]) break return scope_id except: raise def expand_address_inet6(self, addr): """This function expand an IPv6 address to a full description. :param addr: an IPv6 address Return: str. - a full IPv6 address. """ try: prefix, suffix = addr.strip().split('::') num_items = str(prefix).count(':') + str(suffix).count(':') fill_items = 7 - num_items # calculate the items missed in given ip address items = prefix.split(':') for index, item in enumerate(items): if len(item) < 4: items[index] = item.zfill(4) elif len(item) > 4: raise ValueError('Passed IPv6 address is invalid.') prefix = ':'.join(items) items = suffix.split(':') for index, item in enumerate(items): if len(item) < 4: items[index] = item.zfill(4) elif len(item) > 4: raise ValueError('Passed IPv6 address is invalid.') suffix = ':'.join(items) prefix += ':' for i in range(0, fill_items - 1): prefix += '0000:' i += 1 return prefix + suffix except: raise ValueError('Passed IPv6 address is invalid.') def start(self): """This function start a client which can interact with a DTLS server and transmit messages. This function checks whether the certificate and private key are valid. This function depends on DTLSWrap class and the related arguments are configured in the configuration file. """ print('Start client.') print('Checking certificate...') # Check certificate environment, if check failed with errors, try to request (enroll) to the CA. try: ca_path = os.path.expanduser(self.config.get('CACERT', '')) chain_path = os.path.expanduser(self.config.get('CACHAIN', '')) if self.cm.verify_cert_key() and self.cm.verify_cert( chain_path, ca_path, ''): print('Verification of local certificate succeeded.') else: return except Exception as e: print(e) return try: print('Initializing program...') # create a process to refresh 6LoWPAN connection periodically. # if REFLOWPAN <= 0, disable the refresh process. if int(self.config['REFLOWPAN']) > 0: formatter = logging.Formatter(fmt='%(asctime)s\n\t%(message)s', datefmt='%d/%m/%Y %H:%M:%S') rf_logger = self.create_logger('Connection refreshing log', 'rs.log', logging.INFO, formatter) rf = Process(target=self.refresh, args=( float(self.config['REFLOWPAN']), 'sudo rdisc6 lowpan0', rf_logger, )) rf.start() # create a process to synchronize time periodically. # if SYNCTIME <= 0, disable the time synchronization. if int(self.config['SYNCTIME']) > 0: formatter = logging.Formatter(fmt='%(asctime)s\n\t%(message)s', datefmt='%d/%m/%Y %H:%M:%S') sync_logger = self.create_logger('Time synchronization log.', 'sync.log', logging.INFO, formatter) sync = Process(target=self.sync_time, args=( self.config['TIMEZONE'], self.config['TIMESERVER'], float(self.config['SYNCTIME']), sync_logger, )) sync.start() # create a process to run tcpdump tcpdump_lowpan = Process( target=self.run_tcpdump, args=( 'sudo nohup tcpdump -i lowpan0 -ttttnnvvS -w lowpan0.log &', )) tcpdump_lowpan.start() tcpdump_wpan = Process( target=self.run_tcpdump, args=( 'sudo nohup tcpdump -i wpan0 -ttttnnvvS -w wpan0.log &', )) tcpdump_wpan.start() time.sleep(15) # wait for initial time synchronization print('Program initialization finished.') # Create socket for DTLS handshake. self.dw = DTLSWrap.DtlsWrap() print('Read DTLS configuration.') self.dw.init_config(config=self.client_cnf) print('Create DTLS socket.') sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) host = self.config.get('SERVERIP', '::1') port = self.config.get('SERVERPORT', 12345) self.dw.wrap_socket(sock) print('Connect to the sink (DTLS) server: ' + host) print('host: ' + (host.split('%')[0])) # get local ip for 6LoWPAN p = subprocess.Popen('/sbin/ifconfig', stdout=subprocess.PIPE, shell=True) (out, err) = p.communicate() if out is None: out = 'No output!' tmp_pos = out.find('lowpan0') end_pos = tmp_pos + out[tmp_pos:].find( ' prefixlen 64 scopeid 0x20<link>') begin_pos = out[:end_pos].rfind('inet6 ') local_ip = out[(begin_pos + 5):end_pos].strip() print("local_ip: [" + local_ip + "]") # local_ip = "fe80::d9eb:c371:783f:f22" scope_id = self.get_scope_id_inet6(local_ip) print('Scope id: %d ' % scope_id) self.dw.connect((host, int(port), 0, scope_id)) print('Sink server connected.') self.create_conn_log((host, str(port))) # wait a while for other devices to finish DTLS handshake. print('Wait ' + self.config['SYSWAIT'] + ' seconds for other devices to finish DTLS handshake.') time.sleep(float(self.config['SYSWAIT'])) print('Start to send packages.') # Create a logger for recording sending information. formatter = logging.Formatter(fmt='%(asctime)s\t%(message)s', datefmt='%d/%m/%Y %H:%M:%S') self.send_logger = self.create_logger('Sending information log.', 'send.log', logging.INFO, formatter) signal.signal(signal.SIGALRM, self.signal_handler) signal.setitimer(signal.ITIMER_REAL, float(self.config['SENDTIME']), float(self.config['SENDTIME'])) # sleep main function to let signal handlers execute while True: time.sleep(1000) self.dw.shutdown(socket.SHUT_RDWR) self.dw.close() print('DTLS connection closed.') except KeyboardInterrupt: self.dw.shutdown(socket.SHUT_RDWR) self.dw.close() except Exception as e: print(e)
class SinkClientPlain(object): """ This class implements a client which can connect and send messages to sink server via plaintext. This class needs a configuration file 'clien_expcnf' to configure experiment parameters. """ seq = 0 utl = utils.Utils() config = { 'SERVERIP': '', 'SERVERPORT': '', 'TIMEZONE': '', 'SYNCTIME': 0, 'REFLOWPAN': 0, 'SYSWAIT': 0, 'PAYLOADLEN': 0, 'DATE': '', 'SENDTIME': 0, 'SENDRATE': 0, 'DEVNUM': 0 } clientcnf = 'client_expcnf' # the path to configuration file for this client package package_path = '' # the path to this pakcage send_logger = None # a log handler for recording the information of sending messages. sock = 0 addr = None is_first_time = 1 # indicate if it is an initial time synchronization. def __init__(self): self.package_path = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe()))) os.chdir(self.package_path) self.config = self.utl.read_config(self.clientcnf, self.config) def install_dependencies(self): """Install dependencies for experiment on a device. """ self.utl.call('sudo apt-get -y --force-yes install ntp ntpdate', shell=True) self.utl.call('sudo apt-get -y --force-yes install tcpdump', shell=True) self.utl.call('sudo apt-get -y --force-yes install sshpass', shell=True) def create_conn_log(self, dst_addr): """This function creates a connection log file which contains local network interface information and destination address. :param dst_addr: destination address (host, port) :type dst_addr: tuple """ if os.path.exists('conn.log'): self.utl.call('sudo rm -f conn.log', shell=True) print 'Old connection log has been removed.' formatter = logging.Formatter(fmt='%(asctime)s\t\n%(message)s', datefmt='%d/%m/%Y %H:%M:%S') conn_logger = self.create_logger('Connection log.', 'conn.log', logging.INFO, formatter) p = subprocess.Popen('/sbin/ifconfig', stdout=subprocess.PIPE, shell=True) (out, err) = p.communicate() if out is None: out = 'No output!' # get local ip for 6LoWPAN tmp_pos = out.find('lowpan0') end_pos = tmp_pos + out[tmp_pos:].find( ' prefixlen 64 scopeid 0x0<global>') begin_pos = out[:end_pos].rfind('inet6 ') local_ip = out[(begin_pos + 6):end_pos].strip() # local_ip, netmask = host.split()[1].split('/') # inet6 addr: ip/mask Scope:Global print("local_ip: -> " + local_ip) out = out + '\nClient|' + str(local_ip) out = out + '\nSink|' + str(dst_addr[0]) + '.' + str(dst_addr[1]) tail = '\n' + "#" * 40 + '\n' conn_log = out + tail conn_logger.info(conn_log) def create_logger(self, log_name, logfile, level, formatter=None): """Create and return a reference to a logger. This is used to create difference log files in different settings. :param log_name: the name of a log. :param logfile: the file name of the log. :param level: the log level. :param formatter: the format of log items. :type log_name: str. :type logfile: str. :type level: int. :type formatter: logging.Formatter """ logger = logging.getLogger(log_name) logger.setLevel(level) handler = logging.FileHandler(logfile) handler.setFormatter(formatter) logger.addHandler(handler) return logger def signal_handler(self, signum, frame): """This function handles message sending task. """ # send data in some probability (decided from configuration file) per each sending request. # by default, every 100 milliseconds, it tries to send a message. sock = self.sock # socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) packet_per_sec = float(self.config['SENDRATE']) / float( self.config['DEVNUM']) probability = int(packet_per_sec / (1.0 / float(self.config['SENDTIME'])) * 100) coin = random.randint(0, 99) if coin in range( 0, probability ): # coin is in {0,1,...,probability}, then send a message, otherwise, ignore. timestamp = datetime.now().strftime('%H%M%S%f')[:-3] self.seq += 1 # create payload seq_str = str(self.seq).zfill(8) other_len = int( self.config['PAYLOADLEN']) - len(seq_str) - len(timestamp) if other_len < 0: other_len = 0 data = seq_str + '-' * other_len + timestamp if data == '' or data is None: self.send_logger.info( 'DATA ERRORS: the generated date is empty or None.') # print self.addr # local_ip = "fe80::cc1c:954d:e4e6:364f" # sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) # sock.bind((local_ip, 34311, 0, 6)) sent = sock.sendto(data, self.addr) # sent = 1 if sent == 0: self.send_logger.info( 'SENDING ERRORS: sent message length is ' + str(sent) + ' bytes.') raise RuntimeError('Socket connection broken.') else: self.send_logger.info('Sent message length: ' + str(sent) + ' | Message content: ' + data) def refresh(self, timer, cmd, logger): """Refresh net information periodically. If the parameter num is a negative number, the function will execute in endless mode. :param timer: a timer (in seconds) which specifies a period. :param cmd: the scheduled command to execute. :param logger: a handler to record log into a file. :type timer: long :type cmd: str. :type logger: Logger object. """ try: while True: i = 0 while i < 30: p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) (out, err) = p.communicate() if out is None: out = 'No output!' if err is None: err = 'None.' logger.info(out + '\nError (if any): ' + err + '\n=======================') if out.find('No response.') == -1: break else: i += 1 time.sleep(float(timer)) except KeyboardInterrupt: return def sync_time(self, timezone, server_addr, timer, logger): """Synchronize time with a specified server. :param timezone: local timezone. :param server_addr: server IP address. :param timer: a periodical time to do the time synchronization. :param logger: a handler to record log into a file. :type timezone: str. :type server_addr: str. :type timer: float :type logger: Logger object. """ try: while True: print('Time synchronizing...') if self.is_first_time == 1: self.utl.call('sudo date --set=\'' + self.config['DATE'] + '\'', shell=True) self.is_first_time = 0 if not os.path.exists('/usr/share/zoneinfo/' + timezone): print('Error: timezone is invalid.') return self.utl.call('timedatectl set-timezone ' + timezone, shell=True) p = subprocess.Popen('sudo ntpdate -u ' + server_addr, stdout=subprocess.PIPE, shell=True) (out, err) = p.communicate() if err is None: err = 'None.' logger.info(out + '\nError (if any): ' + err + '\n=======================') print('Time synchronization finished.') time.sleep(timer) except KeyboardInterrupt: return def run_tcpdump(self, tcpdump_cmd): """Run tcpdump by using the given command. :param tcpdump_cmd: a command to run tcpdump. :type tcpdump_cmd: str. """ try: subprocess.check_call(tcpdump_cmd, shell=True) except KeyboardInterrupt: return except: raise def get_scope_id_inet6(self, addr): """This function returns the scope id of a given lladdr if the address exists. :param addr: IPv6 address :return: int. scope id of interface. """ scope_id = -1 try: full_addr = self.expand_address_inet6(addr) key = full_addr.replace(':', '') with open('/proc/net/if_inet6', 'r') as ifaces: for line in ifaces: if line.lower().find( key.lower()) != -1: # found the interface scope_id = int(line.split()[1]) break return scope_id except: raise def expand_address_inet6(self, addr): """This function expand an IPv6 address to a full description. :param addr: an IPv6 address Return: str. - a full IPv6 address. """ try: prefix, suffix = addr.strip().split('::') num_items = str(prefix).count(':') + str(suffix).count(':') fill_items = 7 - num_items # calculate the items missed in given ip address items = prefix.split(':') for index, item in enumerate(items): if len(item) < 4: items[index] = item.zfill(4) elif len(item) > 4: raise ValueError('Passed IPv6 address is invalid.') prefix = ':'.join(items) items = suffix.split(':') for index, item in enumerate(items): if len(item) < 4: items[index] = item.zfill(4) elif len(item) > 4: raise ValueError('Passed IPv6 address is invalid.') suffix = ':'.join(items) prefix += ':' for i in range(0, fill_items - 1): prefix += '0000:' i += 1 return prefix + suffix except: raise ValueError('Passed IPv6 address is invalid.') def start(self): """This function start a client which can interact with a sink server via plaintext. This function will start sub-processes to log status, synchronize time and refresh network. """ print('Starting client ...') try: print('Initializing program ...') # create a process to refresh 6LoWPAN connection periodically. # if REFLOWPAN <= 0, disable the refresh process. if int(self.config['REFLOWPAN']) > 0: formatter = logging.Formatter(fmt='%(asctime)s\n\t%(message)s', datefmt='%d/%m/%Y %H:%M:%S') rf_logger = self.create_logger('Connection refreshing log', 'rs.log', logging.INFO, formatter) rf = Process(target=self.refresh, args=( float(self.config['REFLOWPAN']), 'sudo rdisc6 lowpan0', rf_logger, )) rf.start() # create a process to synchronize time periodically. # if SYNCTIME <= 0, disable the time synchronization. if int(self.config['SYNCTIME']) > 0: formatter = logging.Formatter(fmt='%(asctime)s\n\t%(message)s', datefmt='%d/%m/%Y %H:%M:%S') sync_logger = self.create_logger('Time synchronization log.', 'sync.log', logging.INFO, formatter) sync = Process(target=self.sync_time, args=( self.config['TIMEZONE'], self.config['SERVERIP'], float(self.config['SYNCTIME']), sync_logger, )) sync.start() # create a process to run tcpdump tcpdump_lowpan = Process( target=self.run_tcpdump, args=( 'sudo nohup tcpdump -i lowpan0 -ttttnnvvS -w lowpan0.log &', )) tcpdump_lowpan.start() tcpdump_wpan = Process( target=self.run_tcpdump, args=( 'sudo nohup tcpdump -i wpan0 -ttttnnvvS -w wpan0.log &', )) tcpdump_wpan.start() time.sleep(15) # wait for initial time synchronization print('Program initialization finished.') self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) host = self.config['SERVERIP'] port = int(self.config['SERVERPORT']) print('HOST: ' + host) print('PORT: %d' % port) # get local ip for 6LoWPAN p = subprocess.Popen('/sbin/ifconfig', stdout=subprocess.PIPE, shell=True) (out, err) = p.communicate() if out is None: out = 'No output!' tmp_pos = out.find('lowpan0') end_pos = tmp_pos + out[tmp_pos:].find( ' prefixlen 64 scopeid 0x20<link>') begin_pos = out[:end_pos].rfind('inet6 ') local_ip = out[(begin_pos + 5):end_pos].strip() print("local_ip: [" + local_ip + "]") # local_ip = "fe80::d9eb:c371:783f:f22" # local_ip = "fe80::cc1c:954d:e4e6:364f" scope_id = self.get_scope_id_inet6(local_ip) print('Scope id: %d ' % scope_id) # self.dw.connect((host, int(port), 0, scope_id)) # self.sock.bind(('', 34311, 0, scope_id)) # self.sock.bind((local_ip, 34311, 0, scope_id)) self.sock.sendto('start', (host, int(port), 0, scope_id)) # self.sock.bind((local_ip, 34311, 0, scope_id)) print('Sent') # Connect to sink with retransmission enabled, that is requiring acknowledgement. while True: try: self.sock.settimeout(2.0) print('Going to receive...') data, self.addr = self.sock.recvfrom(1024) print('Received!') self.create_conn_log(self.addr) break except socket.timeout: self.sock.sendto('start', (host, port, 0, scope_id)) # wait a while for other devices to start the experiment. print('Wait ' + self.config['SYSWAIT'] + ' seconds to start the experiment.') time.sleep(float(self.config['SYSWAIT'])) print('Start to send packages.') # Create a logger for recording sending information. formatter = logging.Formatter(fmt='%(asctime)s\t%(message)s', datefmt='%d/%m/%Y %H:%M:%S') self.send_logger = self.create_logger('Sending information log.', 'send.log', logging.INFO, formatter) signal.signal(signal.SIGALRM, self.signal_handler) signal.setitimer(signal.ITIMER_REAL, float(self.config['SENDTIME']), float(self.config['SENDTIME'])) # sleep main function to let signal handlers execute while True: time.sleep(1000) except KeyboardInterrupt: pass except Exception as e: print(e)
class Client(object): """ This is a class to implement client functions s.t, obtain certificate from the CA and start client side for DTLS communication with DTLS server. This class needs a configuration file 'clientcnf' to configure certificate, network and other information. This class depends on the certificate management class CertManager. """ utl = utils.Utils() cm = certmngr.CertManager() # for certificate generation config = { 'C': '', 'ST': '', 'L': '', 'O': '', 'OU': '', 'CN': '', 'emailAddress': '', 'ECCPARAM': '', 'CAIP': '', 'CAPORT': '', 'CERT': '', 'CSR': '', 'MSG': '', 'SIG': '', 'CACERT': '', 'SK': '', 'SERVERIP': '', 'SERVERPORT': '', 'CACHAIN': '', 'TYPE': '', 'CERT_REQS': '' } # configuration keywords client_cnf = 'clientcnf' # the path to configuration file for this client package package_path = '' # the path to this package MAX_LEN = 1536 # the max length of packet which can be sent and received def __init__(self): self.package_path = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe()))) os.chdir(self.package_path) self.config = self.utl.read_config(self.client_cnf, self.config) self.cm.init_config(config=self.client_cnf) def init_config(self, **args): """This function initializes the configuration for the class object, where the parameters are read from a configuration file. This function should be called before other (class member) function call. The acceptable arguments are: config, C, ST, L, O, OU, CN, emailAddress, ECCPARAM, CAIP, CAPORT, CERT, CSR, MSG, SIG, CACERT, SK, SERVERIP, SERVERPORT, CACHAIN, TYPE, CERT_REQS. Specifically, the keyword "config" sets the path to configuration file. If arguments are passed to this function, the specified configuration file will be updated. :param args: dictionary of passed arguments. """ if args.get('config', '') != '': self.client_cnf = os.path.expanduser(args['config']) if not os.path.isfile(self.client_cnf): raise IOError('configuration file \"' + self.client_cnf + '\" does not exist or it is not a file.') self.config = self.utl.read_config(self.client_cnf, self.config) # Set run-time configuration which will overwrite the settings in configuration file. update = 0 for key, value in self.config.iteritems(): if args.get(key, '') != '' and args[key] is not None: self.config[key] = args[key] update += 1 if update > 0: self.utl.update_config(self.client_cnf, self.config) # Backup configuration file self.utl.call('cp -f ' + self.client_cnf + ' ' + self.config['CN'] + '.clientcnf.bck', shell=True) def enroll(self): """This function request a certificate from the internal private CA. The function requires the input of three files: CSR, message and manufacturer's signature. Note that these files are configured in the configuration file. """ csr_path = os.path.expanduser(self.config.get('CSR', '')) msg_path = os.path.expanduser(self.config.get('MSG', '')) sig_path = os.path.expanduser(self.config.get('SIG', '')) try: # Check if the required files are accessible if not os.path.isfile(csr_path): raise IOError( 'Path \"' + csr_path + '\" to the CSR file is invalid or it is not a file. Please check ' 'the configuration file \"' + self.client_cnf + '\".') if not os.path.isfile(msg_path): raise IOError( 'Path \"' + msg_path + '\" to the message file is invalid or it is not a file. Please ' 'check the configuration file \"' + self.client_cnf + '\".') if not os.path.isfile(sig_path): raise IOError( 'Path \"' + sig_path + '\" to the signature file is invalid or it is not a file. Please ' 'check the configuration file \"' + self.client_cnf + '\".') # Generate socket and connect to CA through TCP connection sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = self.config.get('CAIP', '127.0.0.1') port = int(self.config.get('CAPORT', 12345)) sock.connect((host, port)) print('CA connected.') # Send three files to CA for certificate generation request. csr = self.utl.read_file(csr_path, 'r') msg = self.utl.read_file(msg_path, 'r') sig = self.utl.read_file(sig_path, 'r') buf = '#$c$#' + csr sock.settimeout(180) # socket timeout after 180 seconds. data_len = sock.send(buf) print str(data_len) + ' bytes sent for CSR file.' ack = sock.recv(self.MAX_LEN) # receive acknowledgement from CA. if ack != 'ok': raise RuntimeError( 'CA\'s acknowledgement for receiving CSR file is failedis failed.' ) buf = '#$m$#' + msg data_len = sock.send(buf) print str(data_len) + ' bytes sent for message file.' ack = sock.recv(self.MAX_LEN) if ack != 'ok': raise RuntimeError( 'CA\'s acknowledgement for receiving message file is failed.' ) buf = '#$s$#' + sig data_len = sock.send(buf) print str(data_len) + ' bytes sent for signature file.' ack = sock.recv(self.MAX_LEN) if ack != 'ok': raise RuntimeError( 'CA\'s acknowledgement for receiving signature file is failed.' ) data = '' # received data ca_cert_ok = False # indicate if CA certificate received. sv_cert_ok = False # indicate if client certificate received. while True: data = data + sock.recv(self.MAX_LEN) if data == 'failed': # Certificate request failed. print( 'Certificate request rejected because provide files may not be valid.' ) raise if data.count('#^#*') == 2 and data.find( '#$t$#') != -1 and data.find('#$a$#') != -1: # Wait the transmission complete then process data. If connection broken, this will timeout after # 180 seconds. extract my certificate begin = data.find('#$t$#') + len('#$t$#') end = data.find('#^#*') path = os.path.dirname(self.config['CERT']) if path != '': self.utl.makedir(path) self.utl.write_file(data[begin:end], self.config['CERT'], 'w') sv_cert_ok = True data = data[end + len('#^#*'):] print('My certificate received and stored at: \"' + self.config['CERT'] + '\".') # extract CA certificate begin = data.find('#$a$#') + len('#$a$#') end = data.rfind('#^#*') path = os.path.dirname(self.config['CACERT']) if path != '': self.utl.makedir(path) self.utl.write_file(data[begin:end], self.config['CACERT'], 'w') ca_cert_ok = True print('CA certificate received and stored at: \"' + self.config['CACERT'] + '\".') if ca_cert_ok and sv_cert_ok: break sock.close() except socket.timeout: print 'Request timeout.' except Exception as e: print(e) def start(self, install_all): """This function start a client which can interact with a DTLS server and transmit messages. This function checks whether the certificate and private key are valid, if not, it automatically request a new certificate from the CA. This function depends on DTLSWrap class and the related arguments are configured in the configuration file. """ if install_all: print('Installing dependencies.') self.utl.call('sudo apt-get -y --force-yes install python-pip', shell=True) self.utl.call('pip install Dtls', shell=True) print('Start client.') print('Checking certificate...') # Check certificate environment, if check failed with errors, try to request (enroll) to the CA. try: ca_path = os.path.expanduser(self.config.get('CACERT', '')) chain_path = os.path.expanduser(self.config.get('CACHAIN', '')) if self.cm.verify_cert_key() and self.cm.verify_cert( chain_path, ca_path, ''): print('Verification of local certificate succeeded.') else: return except Exception as e: print('Certificate environment verification failed.') print( '================================================================================' ) print( 'Trying to request a new certificate from CA based on the local CSR and private key.\n' ) print( 'Note: if new certificate is still invalid, please make sure you have the ' 'correct private key regarding the CSR file.') print( '================================================================================' ) print(e) # Generate CSR file according to the CSR information from the configuration file. # Generate private key if the file is missing. Note: the CSR information must be different from the # previous ones. try: sk_path = os.path.expanduser(self.config.get('SK', '')) if not os.path.isfile(sk_path): self.cm.create_sk() self.cm.create_csr() print('Connect to CA.') self.enroll() print( '================================================================================' ) except Exception as e: print(e) try: # Create socket for DTLS handshake. dw = DTLSWrap.DtlsWrap() print('Read DTLS configuration.') dw.init_config(config=self.client_cnf) print('Create DTLS socket.') sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) host = self.config.get('SERVERIP', '::1') port = self.config.get('SERVERPORT', 12345) dw.wrap_socket(sock) print('Connect to the DTLS server') dw.connect((host, int(port))) # for demo client/server, no multithreading implemented. while True: print('=================================') c_in = raw_input('Enter a message (\'q\' for quite): ') sent = dw.sendto(c_in) if sent == 0: raise RuntimeError('Socket connection broken.') if c_in == 'q': break print('Awaiting message from server...') data = dw.recvfrom(self.MAX_LEN) if data == 'q': break print('Message received: ' + data) dw.shutdown(socket.SHUT_RDWR) dw.close() print('DTLS connection closed.') except KeyboardInterrupt: dw.shutdown(socket.SHUT_RDWR) dw.close() except Exception as e: print(e)
class Sink(object): """ This is a class to implement server functions s.t, accept DTLS client to receive messages. This class needs a configuration file 'server_expcnf' to configure certificate, network and experiment parameters. This class depends on the certificate management class CertManager. """ utl = utils.Utils() cm = certmngr.CertManager() # for certificate generation config = {'CERT': '', 'CACERT': '', 'SK': '', 'SERVERIP': '', 'SERVERPORT': '', 'CACHAIN': '', 'TYPE': '', 'CERT_REQS': 'CERT_REQUIRED'} # configuration keywords server_cnf = 'sink_expcnf' # the path to configuration file for this client package package_path = '' # the path to this pakcage MAX_LEN = 1536 # the max length of packet which can be sent and received def __init__(self): self.package_path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) os.chdir(self.package_path) self.config = self.utl.read_config(self.server_cnf, self.config) self.cm.init_config(config=self.server_cnf) def init_config(self, **args): """This function initializes the configuration for the class object, where the parameters are read from a configuration file. This function should be called before other (class member) function call. The acceptable arguments are: config, CACERT, SK, SERVERIP, SERVERPORT, CACHAIN, TYPE, CERT_REQS. Specifically, the keyword "config" sets the path to configuration file. If arguments are passed to this function, the specified configuration file will be updated. :param args: dictionary of passed arguments. """ if args.get('config', '') != '': self.server_cnf = os.path.expanduser(args['config']) if not os.path.isfile(self.server_cnf): raise IOError('configuration file \"' + self.server_cnf + '\" doesn\'t exist or it is not a file.') self.config = self.utl.read_config(self.server_cnf, self.config) # Set run-time configuration which will overwrite the settings in configuration file. update = 0 for key, value in self.config.iteritems(): if args.get(key, '') != '' and args[key] is not None: self.config[key] = args[key] update += 1 if update > 0: self.utl.update_config(self.server_cnf, self.config) # Backup configuration file self.utl.call('cp -f ' + self.server_cnf + ' ' + self.server_cnf + '.bck', shell=True) def connection_handler(self, sock, addr): """This is a connection handler for subthread. It process a new connection for certificate request. This function takes a client socket for DTLS packets transmission and the parameter addr is used to show the client IP address. This function will be called in individual thread to process a specific client's connection. :param sock: client socket. :param addr: client IP address :type sock: int. :type addr: tuple. """ print ('New connection from: ' + addr[0] + ', ' + str(addr[1])) try: # Create a directory to contain log files generated during the interaction. dt = time.strftime('%d-%m-%Y-%H-%M-%S') # get the date and current time of connection. path = addr[0] # create direct name for this connection's log files. self.utl.makedir('exp/' + path) os.chdir('exp/' + path) # change the current working directory log_name = addr[0] + '--' + dt logging.basicConfig(filename=log_name, level=logging.INFO, format='%(asctime)s\t%(message)s', datefmt='%d/%m/%Y %H:%M:%S') while True: data = sock.recvfrom(self.MAX_LEN) timestamp = datetime.now().strftime('%H%M%S%f')[:-3] # Calculate length of message other than the sequence number and timestamp. # The length of sequence number is fixed 8 bytes. other_len = len(data) - 8 - len(timestamp) # Log client connection information including client (IP, port) and the port assigned to client on sink. conn_info = 'client|' + str(addr[0]) + '.' + str(addr[1]) + '|sink|' + str( self.config['SERVERIP']) + '.' + str(self.config['SERVERPORT']) record = data[0:8] + '\t' + data[8:8 + other_len] + '\t' + data[8 + other_len:] + '\t' + timestamp + \ '\t' + conn_info logging.info(record) except socket.timeout: print ('Time out.') except KeyboardInterrupt: print ('DTLS connection closed.') except Exception as e: print ('Unknown errors.') print (e) finally: sock.shutdown(socket.SHUT_RDWR) sock.close() def start(self): """This function start a server which can interact with a DTLS client and receive messages. This function depends on DTLSWrap class and the related arguments are configured in the configuration file. """ print ('Start server.') print ('Checking certificate...') # Check certificate environment try: ca_path = os.path.expanduser(self.config.get('CACERT', '')) chain_path = os.path.expanduser(self.config.get('CACHAIN', '')) if self.cm.verify_cert_key() and self.cm.verify_cert(chain_path, ca_path, ''): print ('Verification of local certificate succeeded.') else: return except Exception as e: print (e) return try: # Create socket for DTLS handshake. server = DTLSWrap.DtlsWrap() client = DTLSWrap.DtlsWrap() print ('Read DTLS configuration.') server.init_config(config=self.server_cnf) print ('Create DTLS socket.') sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) host = self.config.get('SERVERIP', '::1') port = self.config.get('SERVERPORT', 12345) sock.bind((host, int(port))) server.wrap_socket(sock) # Listen connection request. server.listen(5) print ('Listen DTLS handshake request ...') while True: acc = server.accept() if acc: print ('=================================') print ('Client connected.') client.set_dtls_socket( acc[0]) # acc[0] is SSLSocket object, acc[1] is a tuple (host, port, long, long) p = Process(target=self.connection_handler, args=(client, acc[1],)) p.start() except KeyboardInterrupt: server.shutdown(socket.SHUT_RDWR) server.close() except Exception as e: print (e)
class SinkPlain(object): """ This is a class to implement server functions s.t, accpet client UDP connections to receive plaintext. This class needs a configuration file 'sink_expcnf' to configure certificate, network and experiment parameters. """ utl = utils.Utils() config = {'SERVERIP': '', 'SERVERPORT': ''} # configuration keywords server_cnf = 'sink_expcnf' # the path to configuration file for this client package package_path = '' # the path to this pakcage MAX_LEN = 1536 # the max length of packet which can be sent and received def __init__(self): self.package_path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) os.chdir(self.package_path) self.config = self.utl.read_config(self.server_cnf, self.config) print ('This is a sink server without DTLS.') def init_config(self, **args): """This function initializes the configuration for the class object, where the parameters are read from a configuration file. This function should be called before other (class member) function call. The acceptable arguments are: config, SERVERIP, SERVERPORT. Specifically, the keyword "config" sets the path to configuration file. If arguments are passed to this function, the specified configuration file will be updated. :param args: dictionary of passed arguments. """ if args.get('config', '') != '': self.server_cnf = os.path.expanduser(args['config']) if not os.path.isfile(self.server_cnf): raise IOError('configuration file \"' + self.server_cnf + '\" doesn\'t exist or it is not a file.') self.config = self.utl.read_config(self.server_cnf, self.config) # Set run-time configuration which will overwrite the settings in configuration file. update = 0 for key, value in self.config.iteritems(): if args.get(key, '') != '' and args[key] is not None: self.config[key] = args[key] update += 1 if update > 0: self.utl.update_config(self.server_cnf, self.config) # Backup configuration file self.utl.call('cp -f ' + self.server_cnf + ' ' + self.server_cnf + '..bck', shell=True) def connection_handler(self, addr): """This is a connection handler for subthread. It process a new connection for certificate request. This function will assign a new port to a UDP connection. That is each connection will be bind to a new port. This function will be called in individual thread to process a specific client's connection. :param addr: client IP address :type addr: tuple. """ # sock.settimeout(TIMEOUT) # request timeout after 180 seconds to close the thread. rpl_sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) port = random.randint(10000, 20000) print "bind port: %s" % str(port) rpl_sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) rpl_sock.bind((self.config['SERVERIP'], port)) print 'Binding ' + str((self.config['SERVERIP'], port)) # Log client connection information including client (IP, port) and the port assigned to client on sink. conn_info = 'client|' + str(addr[0]) + '.' + str(addr[1]) + '|sink|' + str(self.config['SERVERIP']) + '.' + str( port) rpl_sock.sendto('ack', addr) print ('New connection from: ' + addr[0] + ', ' + str(addr[1])) try: # Create a directory to contain log files generated during the interaction. dt = time.strftime('%d-%m-%Y-%H-%M-%S') # get the date and current time of connection. path = addr[0] # create direct name for this connection's log files. self.utl.makedir('exp/' + path) os.chdir('exp/' + path) # change the current working directory log_name = addr[0] + '--' + dt logging.basicConfig(filename=log_name, level=logging.INFO, format='%(asctime)s\t%(message)s', datefmt='%d/%m/%Y %H:%M:%S') while True: data, addr = rpl_sock.recvfrom(self.MAX_LEN) timestamp = datetime.now().strftime('%H%M%S%f')[:-3] # Calculate length of message other than the sequence number and timestamp. # The length of sequence number is fixed 8 bytes. other_len = len(data) - 8 - len(timestamp) # Log client connection information including client (IP, port) and the port assigned to client on sink. conn_info = 'client|' + str(addr[0]) + '.' + str(addr[1]) + '|sink|' + str( self.config['SERVERIP']) + '.' + str(port) record = data[0:8] + '\t' + data[8:8 + other_len] + '\t' + data[8 + other_len:] + '\t' + timestamp + \ '\t' + conn_info logging.info(record) except socket.timeout: print ('Time out.') except KeyboardInterrupt: pass except Exception as e: print ('Unknown errors.') print (e) finally: rpl_sock.shutdown(socket.SHUT_RDWR) rpl_sock.close() def start(self): """This function start a server which can accept a DTLS client and receive messages. """ print ('Start server.') try: sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) host = self.config.get('SERVERIP', '::1') port = self.config.get('SERVERPORT', 12345) sock.bind((host, int(port))) # Listen connection request. while True: data, addr = sock.recvfrom(1024) print ('=================================') print ('Client connected.') p = Process(target=self.connection_handler, args=(addr,)) p.start() except KeyboardInterrupt: sock.shutdown(socket.SHUT_RDWR) sock.close() except Exception as e: print (e)
class OCSP(object): """This class manipulate OCSP server which responds the online certificate status request. The parameters used by this class is assigned in the configuration file "ocspcnf". The configuration file allows to set the network information, local certificate information and etc. """ utl = utils.Utils() config = { 'CACERT': '', 'OCSPPORT': '', 'OCSPCERT': '', 'OCSPSK': '', 'CERTDB': '' } # configuration keywords app_cnf = 'ocspcnf' # the path to configuration file for this class package_path = '' # the path to this package def __init__(self): self.package_path = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe()))) os.chdir(self.package_path) def init_config(self, **kwargs): """Initialize the package configuration according to the configuration file. This function MUST be called before other function call. The acceptable keywords are: config, CACERT, OCSPPORT, OCSPCERT, OCSPSK, CERTDB. Specifically, "config" is to set the path to configuration file. If arguments are passed to this function, the specified configuration file will be updated. :param kwargs: dictionary of passed arguments. """ if kwargs.get('config', '') != '': self.app_cnf = os.path.expanduser(kwargs['config']) if not os.path.isfile(self.app_cnf): raise IOError('configuration file \"' + self.app_cnf + '\" doesn\'t exist or it is not a file.') self.config = self.utl.read_config(self.app_cnf, self.config) # Set run-time configuration which will overwrite the settings in configuration file. update = 0 for key, value in self.config.iteritems(): if kwargs.get(key, '') != '' and kwargs[key] is not None: self.config[key] = kwargs[key] update += 1 if update > 0: self.utl.update_config(self.app_cnf, self.config) def start(self): """This function starts an OCSP server according to the configuration. """ port = self.config.get('OCSPPORT', '') db_path = os.path.expanduser(self.config.get('CERTDB', '')) signer_path = os.path.expanduser(self.config.get('OCSPCERT', '')) key_path = os.path.expanduser(self.config.get('OCSPSK', '')) ca_path = os.path.expanduser(self.config.get('CACERT', '')) try: if not os.path.isfile(signer_path): raise IOError( 'Path \"' + signer_path + '\" to OCSP certificate is invalid or it is not a file.') if not os.path.isfile(key_path): raise IOError( 'Path \"' + key_path + '\" to OCSP server private key is invalid or it is not a file.' ) if not os.path.isfile(db_path): raise IOError( 'Path \"' + db_path + '\" to the simulated global CA\'s database is invalid or it is ' 'not a file.') if not os.path.isfile(ca_path): raise IOError( 'Path \"' + ca_path + '\" to the simulated global CA\'s certificate is invalid or it ' 'is not a file.') if port != '': self.utl.check_call('openssl ocsp -index ' + db_path + ' -port ' + port + ' -rsigner ' + signer_path + ' -rkey ' + key_path + ' -CA ' + ca_path + ' -text -out log.txt', shell=True) else: print('Error: port to OCSP server is not set.') raise except KeyboardInterrupt: pass except Exception as e: print(e)
class CA(object): """This class implement CA functions which start CA and handle certificate generation requests. The parameters used by this class is assigned in the configuration file "appcacnf". The configuration file allows to set the network information, local certificate information and etc. Such information is used to perform request verification and generate certificates. This class depends on the certificate management module which indeed generates certificate. """ utl = utils.Utils() config = { 'IP': '', 'PORT': '', 'CERT': 'tmpcert.pem', 'CSR': 'tmpcsr.csr', 'MSG': 'tmpmsg', 'SIG': 'tmpsig', 'CACERT': '', 'CACHAIN': '', 'OCSP': '', 'OCSPPORT': '', 'SIGCERT': '', 'SIGCHAIN': '', 'OCSPCERT': '', 'OCSPSK': '', 'SELFSIGN': '', 'OPENSSL_PATH': '' } # configuration keywords app_cnf = 'appcacnf' # the path to configuration file for this package package_path = '' # the path to this pakcage MAX_LEN = 1536 # the max length of packet which can be sent and received def __init__(self): self.package_path = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe()))) os.chdir(self.package_path) self.config = self.utl.read_config(self.app_cnf, self.config) def init_config(self, **args): """Initialize the package configuration according to the configuration file. This function MUST be called before other function call. The acceptable keywords are: config, IP, PORT, CERT, CSR, MSG, SIG, CACERT, CACHAIN, OCSP, OCSPPORT, SIGCERT, SIGCHAIN, OCSPCERT, OCSPSK, SELFSIGN, OPENSSL_PATH. Specifically, "config" is to set the path to configuration file. If arguments are passed to this function, the specified configuration file will be updated. :param args: dictionary of passed arguments. """ if args.get('config', '') != '': self.certcnf = os.path.expanduser(args['config']) if not os.path.isfile(self.certcnf): raise IOError('configuration file \"' + self.certcnf + '\" doesn\'t exist or it is not a file.') self.config = self.utl.read_config(self.certcnf, self.config) # Set run-time configuration which will overwrite the settings in configuration file. update = 0 for key, value in self.config.iteritems(): if args.get(key, '') != '' and args[key] is not None: self.config[key] = args[key] update += 1 if args['OCSPPORT'] is not None and args.get( 'OCSPPORT', '') != '': # change ocsp server url url = self.config['OCSP'] url = url[:str(url).rfind(':') + 1] # get IP address self.config['OCSP'] = url + args['OCSPPORT'] if update > 0: self.utl.update_config(self.app_cnf, self.config) def write_file(self, data, filename): """This function write a file in 'w' mode. :param data: content to write in the file. :param filename: path to te output file. :type data: str. :type filename: str. """ f = open(filename, 'w') f.write(data) f.close() def connection_handler(self, sock, addr): """This is a connection handler for subthread. It process a new connection for certificate request. This function takes a client socket for DTLS packets transmission and the parameter addr is used to show the client IP address. This function will be called in individual thread to process a specific client's connection. :param sock: client socket. :param addr: client IP address :type sock: int. :type addr: tuple. """ sock.settimeout( 180) # request timeout after 180 seconds to close the thread. print('===== New connection from: ' + addr[0]) try: # Create a directory to contain temporary files (e.g., certificate and received files) # generated during the interaction. path = ''.join( random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) cm = certmngr.CertManager() cm.init_config(config=self.app_cnf) self.utl.makedir('tmp/' + path) os.chdir('tmp/' + path) # change the current working directory # The While loop processes enroll request according to the designed message follow. # The specified indicators like 'c' and 'm' may be changed in later version. It is currently for the test # and simplicity. while True: data = sock.recv(self.MAX_LEN) if data.startswith( '#$c$#' ): # it is a CSR string and then write it to a file. self.write_file(data[len('#$c$#'):], self.config['CSR']) print('CSR file received.') sock.send('ok') # send acknowledgement to the applicant if data.startswith( '#$m$#' ): # it is a message string and then write it to a file. self.write_file(data[len('#$m$#'):], self.config['MSG']) print('Message file received.') sock.send('ok') if data.startswith( '#$s$#' ): # it is a signature file then check if it is valid. self.write_file(data[len('#$s$#'):], self.config['SIG']) print('Signature file received.') sock.send('ok') if not cm.verify_sig( ): # signature verification failed and send result to applicant. sock.send('failed') print( 'Certificate request rejected: manufacturer\'s signature verification failed.' ) break else: # signature verified so that generate certificate. print('Manufacturer\'s signature verified.') if not cm.verify_cert(self.config.get('SIGCHAIN', ''), self.config.get('SIGCERT', ''), self.config.get('OCSP', '')): return cm.create_cert() cert_path = cm.find_cert( ) # path to the generated certificate. cert = self.utl.read_file(cert_path, 'r') cert = '#$t$#' + cert + '#^#*' sock.send(cert) # send client certificate. print('Client certificate delivered.') ca_path = os.path.expanduser( self.config.get('CACERT', '')) # path to CA certificate if not os.path.isfile(ca_path): raise IOError( 'Path \"' + ca_path + '\" to the CA certificate is invalid or it is not a ' 'file. Please check the configuration file \"' + self.app_cnf + '\".') cert = self.utl.read_file(ca_path, 'r') cert = '#$a$#' + cert + '#^#*' sock.send(cert) # send CA certificate. print('CA certificate delivered.') break except socket.timeout: pass except KeyboardInterrupt: sock.shutdown(socket.SHUT_RDWR) sock.close() except Exception as e: print(e) finally: # delete temporary directory to clean up session. print('Clean up...') self.utl.call('rm -rf ../' + path, shell=True) print('Session completed.') def start(self): """This function starts the CA to process certificate generation requests. This function support multi-thread processing so that requests can be handle in parallel. """ try: # create socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = self.config.get('IP', '127.0.0.1') port = int(self.config.get('PORT', 12345)) sock.bind((host, port)) # listen requests sock.listen(5) print('CA started and awaiting request ...') while True: cnsock, addr = sock.accept() # create a new thread to handle the accepted connection. p = Process(target=self.connection_handler, args=( cnsock, addr, )) p.start() except KeyboardInterrupt: sock.shutdown(socket.SHUT_RDWR) sock.close() except Exception as e: print(e)