2a83c97b9aaea18fb6e681fd92c450526ed8f227
[openflowplugin.git] / xml_validator.py
1 '''
2 Created on Jan 24, 2014
3
4 @author: vdemcak
5 '''
6
7 import logging
8 import os
9 import re
10 from xml.etree import ElementTree as ET
11
12
13 class Loader():
14
15     @staticmethod
16     def loadXml(file_name):
17         path_to_xml = os.path.join('', file_name)
18         path_to_xml = os.path.abspath(__file__ + '/../../../' + path_to_xml)
19         with open(path_to_xml) as f:
20                 xml_string = f.read()
21                 xml_string = re.sub(' xmlns="[^"]+"', '', xml_string, count=1)
22
23         tree = ET.fromstring(xml_string)
24         return tree, xml_string
25
26     @staticmethod
27     def buildXmlDocDictionaryForComarableElements(element, flow_dict, p_elm_name=None, kwd=None, akwd=None, mkwd=None):
28         act_key_dict = kwd if (kwd > None) else akwd if (akwd > None) else mkwd if (mkwd > None) else None
29         if element > None :
30             elm_alias = element.tag if (act_key_dict.get(element.tag, None) > None) else None
31             if ((element.getchildren() > None) & (len(element.getchildren()) > 0)):
32                 for child in element.getchildren() :
33                     if (element.tag == 'match') :
34                         Loader.buildXmlDocDictionaryForComarableElements(child, flow_dict, mkwd=mkwd)
35                     elif (element.tag == 'actions') :
36                         Loader.buildXmlDocDictionaryForComarableElements(child, flow_dict, akwd=akwd)
37                     else :
38                         Loader.buildXmlDocDictionaryForComarableElements(child, flow_dict, elm_alias, kwd, akwd, mkwd);
39             else :
40                 if element.text > None :
41                     text = re.sub( '[\s]+','', element.text, count=1)
42                     a_key = p_elm_name if (p_elm_name > None) else element.tag
43                     flow_dict[a_key] = text;
44         return
45
46 type_int = 1
47 type_boolean = 2
48 type_ethernet = 3
49 type_ipv4 = 4
50 type_ipv6 = 5
51
52 class Field():
53     """
54         fields to check, arguments:
55         key: element tag from keywords and xml
56         bits: expected length in bits
57         prerequisites: dictionary of elements tag from xml which are required for this field and their values in list
58                        or [None] if value is undefined or it's irrelevant (we just need to check if tag is set)
59         convert_from: format in which is value, that is checked against prerequisite values stored in xml
60
61         e.g.:
62         key:'ipv4-source'
63         bits:32
64         prerequisites: {'ethernet-type': [2048]}
65         convert_from: 10
66
67         OF_IPV4_SRC = Field('ipv4-source', 32, {'ethernet-type': [2048]}, 10)
68         IN_PHY_PORT = Field('in-phy-port', 32, {'in-port': [None]}, 10)
69     """
70
71     def __init__(self, key, bits, prerequisites=None, convert_from=10, value_type=type_int):
72         self.key = str(key)
73         self.bits = bits
74         if prerequisites is not None:
75             self.prerequisites = dict(prerequisites)
76         else:
77             self.prerequisites = None
78         self.convert_from = convert_from
79         self.value_type = value_type
80
81     def __str__(self):
82         return "Field: {0}, size: {1}, prerequisites: {2}"\
83             .format(self.key, self.bits, self.prerequisites)
84
85
86 class XMLValidator():
87
88     log = logging.getLogger('XMLValidator')
89     log.propagate=False
90     channel = logging.StreamHandler()
91     log.addHandler(channel)
92
93     def __init__(self, kwd, akwd, mkwd, loglevel=logging.INFO):
94
95         self.test_name = 'No test loaded'
96         XMLValidator.log.setLevel(loglevel)
97
98         self.xml_ok = True
99         self.fields = list()
100         self.flow_dict = dict()
101
102         self.kwd = kwd
103         self.akwd = akwd
104         self.mkwd = mkwd
105
106     def create_dictionaries(self, file_name):
107         self.test_name = file_name
108         
109         formatter = logging.Formatter('TEST {0}: %(levelname)s: %(message)s'.format(self.test_name))
110         XMLValidator.channel.setFormatter(formatter)
111
112         self.flow_dict = dict()
113         treeXml1, self.xml_string = Loader.loadXml(file_name)
114         Loader.buildXmlDocDictionaryForComarableElements(treeXml1, self.flow_dict, kwd=self.kwd, akwd=self.akwd, mkwd=self.mkwd)
115         XMLValidator.log.debug('loaded dict from xml: {0}'.format(self.flow_dict))
116
117
118     def fill_fields(self):
119         Matchers.fill_validator(self)
120
121     def add_field(self, fields):
122         self.fields.append(fields)
123
124     def integer_check(self, value, bits, convert_from=10):
125         XMLValidator.log.debug('validating integer: {0}'.format(value))
126         if (int(value, convert_from) / 2**bits) > 0:
127             XMLValidator.log.error('value: {0} is larger than expected: {1}'.format(value, 2**bits))
128             raise StandardError
129
130     def boolean_check(self, value, bits):
131         XMLValidator.log.debug('validating boolean: {0}'.format(value))
132         if bits < 1:
133             XMLValidator.log.error('value: {0} is larger than expected: {1}'.format(value, 2**bits))
134             raise StandardError
135
136     def ethernet_check(self, a):
137         XMLValidator.log.debug('validating ethernet address: {0}'.format(a))
138         numbers = a.split(':')
139         max_range = (2**8) - 1
140
141         for n in numbers:
142             if int(n, 16) > max_range:
143                 XMLValidator.log.error('octet: {0} in ethernet address: {1} larger than: {2}'.format(n, a, max_range))
144                 raise StandardError
145
146     def mask_check(self, address, mask, base=16, part_len=16, delimiter=':'):
147         if (int(mask) % part_len) != 0:
148             raise StandardError('{0} is not valid mask, should be multiples of {1}'.format(mask, part_len))
149
150         part_count = int(mask) / part_len
151
152         for part in address.split(delimiter):
153             part_value = int(part, base) if part != '' else 0
154             part_count -= 1
155
156             if part_count < 0 and part_value != 0:
157                 raise StandardError('address part {0} should be 0'.format(part))
158
159     def ipv4_check(self, a):
160         XMLValidator.log.debug('validating ipv4 address: {0}'.format(a))
161
162         mask_pos = a.find('/')
163         if mask_pos > 0:
164             a_mask = a[mask_pos + 1:]
165             a = a[:mask_pos]
166             self.mask_check(a, a_mask, 10, 8, '.')
167
168         numbers = a.split('.')
169         max_range = (2**8) - 1
170
171         for n in numbers:
172             if int(n) > max_range:
173                 raise StandardError('octet: {0} in ipv4 address: {1} larger than: {2}'.format(n, a, max_range))
174
175     def ipv6_check(self, a):
176         XMLValidator.log.debug('validating ipv6 address: {0}'.format(a))
177         mask_pos = a.find('/')
178         if mask_pos > 0:
179             a_mask = a[mask_pos + 1:]
180             a = a[:mask_pos]
181             self.mask_check(a, a_mask)
182
183         numbers = a.split(':')
184         max_range = (2**16) - 1
185
186         for n in numbers:
187             #if n == '' then the number is 0000 which is always smaller than max_range
188             if n != '' and int(n, 16) > max_range:
189                 raise StandardError('number: {0} in ipv6 address: {1} larger than: {2}'.format(n, a, max_range))
190
191     def check_size(self, value, bits, value_type, convert_from=10):
192         XMLValidator.log.debug('checking value: {0}, size should be {1} bits'.format(value, bits))
193         ipv6_regexp = re.compile("^[0-9,A-F,a-f]{0,4}(:[0-9,A-F,a-f]{0,4}){1,7}(/[0-9]{1,3})?$")
194         ipv4_regexp = re.compile("^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$")
195         ethernet_regexp = re.compile("^[0-9,A-F,a-f]{2}(:[0-9,A-F,a-f]{2}){5}$")
196
197         try:
198             if value_type == type_boolean and value in ['true', 'false']:  #boolean values
199                     self.boolean_check(value, bits)
200             elif value_type == type_ethernet and ethernet_regexp.match(value):  #ethernet address
201                 self.ethernet_check(value)
202             elif value_type == type_ipv4 and ipv4_regexp.match(value):  #IPV4 address
203                 self.ipv4_check(value)
204             elif value_type == type_ipv6 and ipv6_regexp.match(value):  #IPV6 address
205                 self.ipv6_check(value)
206             elif value_type == type_int:  #integer values
207                 self.integer_check(value, bits, convert_from)
208             else:
209                 raise StandardError
210
211             XMLValidator.log.info('size of: {0} < 2^{1} validated successfully'.format(value, bits))
212
213         except ValueError:
214             XMLValidator.log.error('problem converting value to int or IP addresses: {0}'.format(value))
215             self.xml_ok = False
216
217         except TypeError:
218             XMLValidator.log.error('problem converting value: {0}, TypeError'.format(value))
219             self.xml_ok = False
220
221         except StandardError as e:
222             XMLValidator.log.error('problem checking size for value: {0}, reason: {1}'.format(value, str(e)))
223             self.xml_ok = False
224
225
226     def has_prerequisite(self, key, values, convert_from, flow_dict):
227         XMLValidator.log.debug('checking prerequisite: {0} - {1}'.format(key, values))
228         try:
229             flow_value_raw = flow_dict[key]
230
231             #if prerequisites values are [None] we don't care about actual value
232             if values != [None]:
233                 flow_value = int(flow_value_raw, convert_from)
234
235                 if flow_value not in values:
236                     raise StandardError()
237
238             XMLValidator.log.info('prerequisite {0}: {1} to value {2} validated successfully'.format(key, values, flow_value_raw))
239
240         except KeyError:
241             XMLValidator.log.error('can\'t find element: {0} in xml {1} or in keywords {2}'.format(key, self.xml_string, self.mkwd.keys()))
242             self.xml_ok = False
243
244         except ValueError or TypeError:
245             # flow_value_raw is string that cannot be converted to decimal or hex number or None
246             if flow_value_raw not in values:
247                 XMLValidator.log.error('can\'t find element: {0} with value value: {1} '
248                                'in expected values {2}'.format(key, flow_value_raw, values))
249                 self.xml_ok = False
250             else:
251                 XMLValidator.log.info('prerequisite {0}: {1} to value {2} validated successfully'.format(key, values, flow_value_raw))
252
253         except StandardError:
254             XMLValidator.log.error('can\'t find element: {0} with value value: {1} '
255                            'in expected values {2}'.format(key, flow_value, values))
256             self.xml_ok = False
257
258     def check_all_prerequisites(self, prerequisites_dict, convert_from, flow_dict):
259         XMLValidator.log.debug('checking prerequisites: {0}'.format(prerequisites_dict))
260         for k, v in prerequisites_dict.items():
261             self.has_prerequisite(k, v, convert_from, flow_dict)
262
263     def check_single_field(self, field, flow_dict):
264         """
265         @type field MatchField
266         @type flow_dict dict
267         """
268
269         if field.key not in flow_dict:
270             XMLValidator.log.debug('{0} is not set in XML, skipping validation'.format(field.key))
271             return
272         else:
273             XMLValidator.log.info('validating: {0}'.format(field))
274
275         if field.bits is not None:
276             self.check_size(flow_dict[field.key], field.bits, field.value_type, field.convert_from)
277
278         if field.prerequisites is not None:
279             self.check_all_prerequisites(field.prerequisites, field.convert_from, flow_dict)
280
281     def validate_fields(self):
282         self.xml_ok = True
283         XMLValidator.log.info('validating against flow: {0}'.format(self.flow_dict))
284         for field in self.fields:
285             self.check_single_field(field, self.flow_dict)
286
287     def validate_misc_values(self):
288         for kw in self.kwd.keys():
289             if kw in self.flow_dict.keys():
290                 XMLValidator.log.info('validating: {0}: {1}'.format(kw, self.flow_dict[kw]))
291                 try:
292                     value = int(self.flow_dict[kw])
293                     if value < 0:
294                         XMLValidator.log.error('value: {0}: {1} should be non-negative'.format(kw, self.flow_dict[kw]))
295                         self.xml_ok = False
296                     else:
297                         XMLValidator.log.info('value: {0}: {1} validated successfully'.format(kw, self.flow_dict[kw]))
298                 except StandardError:
299                     XMLValidator.log.error('can\'t convert value: {0}: {1} to integer'.format(kw, self.flow_dict[kw]))
300                     self.xml_ok = False
301             else:
302                 XMLValidator.log.debug('{0} is not set in XML, skipping validation'.format(kw))
303
304     def validate(self):
305         self.validate_fields()
306         self.validate_misc_values()
307
308         XMLValidator.log.info('XML valid: {0}'.format(self.xml_ok))
309
310         return self.xml_ok
311
312 class Matchers():
313
314     IN_PORT = Field('in-port', 32)
315     IN_PHY_PORT = Field('in-phy-port', 32, {'in-port': [None]})
316     METADATA = Field('metadata', 64, convert_from=16)
317
318     ETH_DST = Field('ethernet-source', 48, value_type=type_ethernet)
319     ETH_SRC = Field('ethernet-destination', 48, value_type=type_ethernet)
320     ETH_TYPE = Field('ethernet-type', 16)
321
322     VLAN_VID = Field('vlan-id', 13)
323     VLAN_PCP = Field('vlan-pcp', 3, {'vlan-id': [None]})
324
325     IP_DSCP = Field('ip-dscp', 6, {'ethernet-type': [2048, 34525]})
326     IP_ENC = Field('ip-ecn', 2, {'ethernet-type': [2048, 34525]})
327     IP_PROTO = Field('ip-protocol', 8, {'ethernet-type': [2048, 34525]})
328
329     IPV4_SRC = Field('ipv4-source', 32, {'ethernet-type': [2048]}, value_type=type_ipv4)
330     IPV4_DST = Field('ipv4-destination', 32, {'ethernet-type': [2048]}, value_type=type_ipv4)
331
332     TCP_SRC = Field('tcp-source-port', 16, {'ip-protocol': [6]})
333     TCP_DST = Field('tcp-destination-port', 16, {'ip-protocol': [6]})
334     UDP_SRC = Field('udp-source-port', 16, {'ip-protocol': [17]})
335     UDP_DST = Field('udp-destination-port', 16, {'ip-protocol': [17]})
336     SCTP_SRC = Field('sctp-source-port', 16, {'ip-protocol': [132]})
337     SCTP_DST = Field('sctp-destination-port', 16, {'ip-protocol': [132]})
338     ICMPV4_TYPE = Field('icmpv4-type', 8, {'ip-protocol': [1]})
339     ICMPV4_CODE = Field('icmpv4-code', 8, {'ip-protocol': [1]})
340
341     ARP_OP = Field('arp-op', 16, {'ethernet-type': [2054]})
342     ARP_SPA = Field('arp-source-transport-address', 32, {'ethernet-type': [2054]}, value_type=type_ipv4)
343     ARP_TPA = Field('arp-target-transport-address', 32, {'ethernet-type': [2054]}, value_type=type_ipv4)
344     ARP_SHA = Field('arp-source-hardware-address', 48, {'ethernet-type': [2054]}, value_type=type_ethernet)
345     ARP_THA = Field('arp-target-hardware-address', 48, {'ethernet-type': [2054]}, value_type=type_ethernet)
346
347     IPV6_SRC = Field('ipv6-source', 128, {'ethernet-type': [34525]}, value_type=type_ipv6)
348     IPV6_DST = Field('ipv6-destination', 128, {'ethernet-type': [34525]}, value_type=type_ipv6)
349     IPV6_FLABEL = Field('ipv6-flabel', 20, {'ethernet-type': [34525]})
350
351     ICMPV6_TYPE = Field('icmpv6-type', 8, {'ip-protocol': [58]})
352     ICMPV6_CODE = Field('icmpv6-code', 8, {'ip-protocol': [58]})
353
354     IPV6_ND_TARGET = Field('ipv6-nd-target', 128, {'icmpv6-type': [135, 136]}, value_type=type_ipv6)
355     IPV6_ND_SLL = Field('ipv6-nd-sll', 48, {'icmpv6-type': [135]}, value_type=type_ethernet)
356     IPV6_ND_TLL = Field('ipv6-nd-tll', 48, {'icmpv6-type': [136]}, value_type=type_ethernet)
357
358     MPLS_LABEL = Field('mpls-label', 20, {'ethernet-type': [34887, 34888]})
359     MPLS_TC = Field('mpls-tc', 3, {'ethernet-type': [34887, 34888]})
360     MPLS_BOS = Field('mpls-bos', 1, {'ethernet-type': [34887, 34888]})
361
362     PBB_ISID = Field('pbb-isid', 24, {'ethernet-type': [35047]})
363     TUNNEL_ID = Field('tunnel-id', 64)
364     IPV6_EXTHDR = Field('ipv6-exthdr', 9, {'ethernet-type': [34525]})
365
366
367     @staticmethod
368     def fill_validator(validator):
369         """
370         @type validator XMLValidator
371         """
372
373         validator.add_field(Matchers.IN_PORT)
374         validator.add_field(Matchers.IN_PHY_PORT)
375         validator.add_field(Matchers.METADATA)
376         validator.add_field(Matchers.ETH_DST)
377         validator.add_field(Matchers.ETH_SRC)
378         validator.add_field(Matchers.ETH_TYPE)
379         #validator.add_field(Matchers.VLAN_VID) - incorrenct XML parsing, if vlan-id-present is present its overriden by it, need to fix loader
380         validator.add_field(Matchers.VLAN_PCP)
381         validator.add_field(Matchers.IP_DSCP)
382         validator.add_field(Matchers.IP_ENC)
383         validator.add_field(Matchers.IP_PROTO)
384         validator.add_field(Matchers.IPV4_SRC)
385         validator.add_field(Matchers.IPV4_DST)
386         validator.add_field(Matchers.TCP_SRC)
387         validator.add_field(Matchers.TCP_DST)
388         validator.add_field(Matchers.UDP_SRC)
389         validator.add_field(Matchers.UDP_DST)
390         validator.add_field(Matchers.SCTP_SRC)
391         validator.add_field(Matchers.SCTP_DST)
392         validator.add_field(Matchers.ICMPV4_TYPE)
393         validator.add_field(Matchers.ICMPV4_CODE)
394         validator.add_field(Matchers.ARP_OP)
395         validator.add_field(Matchers.ARP_SPA)
396         validator.add_field(Matchers.ARP_TPA)
397         validator.add_field(Matchers.ARP_SHA)
398         validator.add_field(Matchers.ARP_THA)
399         validator.add_field(Matchers.IPV6_SRC)
400         validator.add_field(Matchers.IPV6_DST)
401         validator.add_field(Matchers.IPV6_FLABEL)
402         validator.add_field(Matchers.ICMPV6_TYPE)
403         validator.add_field(Matchers.ICMPV6_CODE)
404         validator.add_field(Matchers.IPV6_ND_TARGET)
405         validator.add_field(Matchers.IPV6_ND_SLL)
406         validator.add_field(Matchers.IPV6_ND_TLL)
407         validator.add_field(Matchers.MPLS_LABEL)
408         validator.add_field(Matchers.MPLS_TC)
409         validator.add_field(Matchers.MPLS_BOS)
410         validator.add_field(Matchers.PBB_ISID)
411         validator.add_field(Matchers.TUNNEL_ID)
412         validator.add_field(Matchers.IPV6_EXTHDR)
413
414
415 if __name__ == '__main__':
416
417     keywords = None
418     with open(os.path.abspath(__file__ + '/../../../keywords.csv')) as f:
419         keywords = dict(line.strip().split(';') for line in f if not line.startswith('#'))
420
421     #print keywords
422
423     match_keywords = None
424     with open(os.path.abspath(__file__ + '/../../../match-keywords.csv')) as f:
425         match_keywords = dict(line.strip().split(';') for line in f if not line.startswith('#'))
426
427     #print match_keywords
428
429     action_keywords = None
430     with open(os.path.abspath(__file__ + '/../../../action-keywords.csv')) as f:
431         action_keywords = dict(line.strip().split(';') for line in f if not line.startswith('#'))
432
433     paths_to_xml = list()
434     for i in range(1, 50):
435         #paths_to_xml = ['xmls/f5.xml', 'xmls/f14.xml', 'xmls/f23.xml', 'xmls/f25.xml']
436         paths_to_xml.append('xmls/f%d.xml' % i)
437
438     validator = XMLValidator(keywords, action_keywords, match_keywords, logging.ERROR)
439     validator.fill_fields()
440
441     for path in paths_to_xml:
442         validator.create_dictionaries(path)
443         validator.validate()
444