Exemplo n.º 1
0
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
Exemplo n.º 2
0
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)
Exemplo n.º 3
0
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)
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
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)
Exemplo n.º 6
0
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)
Exemplo n.º 7
0
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)
Exemplo n.º 8
0
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)