2 Created on Jan 24, 2014
7 from exceptions import StandardError
11 from xml.etree import ElementTree as ET
17 def loadXml(file_name):
18 path_to_xml = os.path.join('', file_name)
19 path_to_xml = os.path.abspath(__file__ + '/../../../' + path_to_xml)
20 with open(path_to_xml) as f:
22 xml_string = re.sub(' xmlns="[^"]+"', '', xml_string, count=1)
24 tree = ET.fromstring(xml_string)
25 return tree, xml_string
28 def buildXmlDocDictionaryForComarableElements(element, flow_dict, p_elm_name=None, kwd=None, akwd=None, mkwd=None):
29 act_key_dict = kwd if (kwd > None) else akwd if (akwd > None) else mkwd if (mkwd > None) else None
31 elm_alias = element.tag if (act_key_dict.get(element.tag, None) > None) else None
32 if ((element.getchildren() > None) & (len(element.getchildren()) > 0)):
33 for child in element.getchildren():
34 if (element.tag == 'match'):
35 Loader.buildXmlDocDictionaryForComarableElements(child, flow_dict, mkwd=mkwd)
36 elif (element.tag == 'actions'):
37 Loader.buildXmlDocDictionaryForComarableElements(child, flow_dict, akwd=akwd)
39 Loader.buildXmlDocDictionaryForComarableElements(child, flow_dict, elm_alias, kwd, akwd, mkwd)
41 if element.text > None:
42 text = re.sub('[\s]+', '', element.text, count=1)
43 a_key = p_elm_name if (p_elm_name > None) else element.tag
44 flow_dict[a_key] = text
56 fields to check, arguments:
57 key: element tag from keywords and xml
58 bits: expected length in bits
59 prerequisites: dictionary of elements tag from xml which are required for this field and their values in list
60 or [None] if value is undefined or it's irrelevant (we just need to check if tag is set)
61 convert_from: format in which is value, that is checked against prerequisite values stored in xml
66 prerequisites: {'ethernet-type': [2048]}
69 OF_IPV4_SRC = Field('ipv4-source', 32, {'ethernet-type': [2048]}, 10)
70 IN_PHY_PORT = Field('in-phy-port', 32, {'in-port': [None]}, 10)
73 def __init__(self, key, bits, prerequisites=None, convert_from=10, value_type=type_int):
76 if prerequisites is not None:
77 self.prerequisites = dict(prerequisites)
79 self.prerequisites = None
80 self.convert_from = convert_from
81 self.value_type = value_type
84 return "Field: {0}, size: {1}, prerequisites: {2}"\
85 .format(self.key, self.bits, self.prerequisites)
90 log = logging.getLogger('XMLValidator')
92 channel = logging.StreamHandler()
93 log.addHandler(channel)
95 def __init__(self, kwd, akwd, mkwd, loglevel=logging.INFO):
97 self.test_name = 'No test loaded'
98 XMLValidator.log.setLevel(loglevel)
102 self.flow_dict = dict()
108 def create_dictionaries(self, file_name):
109 self.test_name = file_name
111 formatter = logging.Formatter('TEST {0}: %(levelname)s: %(message)s'.format(self.test_name))
112 XMLValidator.channel.setFormatter(formatter)
114 self.flow_dict = dict()
115 treeXml1, self.xml_string = Loader.loadXml(file_name)
116 Loader.buildXmlDocDictionaryForComarableElements(
117 treeXml1, self.flow_dict, kwd=self.kwd, akwd=self.akwd, mkwd=self.mkwd)
118 XMLValidator.log.debug('loaded dict from xml: {0}'.format(self.flow_dict))
120 def fill_fields(self):
121 Matchers.fill_validator(self)
123 def add_field(self, fields):
124 self.fields.append(fields)
126 def integer_check(self, value, bits, convert_from=10):
127 XMLValidator.log.debug('validating integer: {0}'.format(value))
128 if (int(value, convert_from) / 2**bits) > 0:
129 XMLValidator.log.error('value: {0} is larger than expected: {1}'.format(value, 2**bits))
132 def boolean_check(self, value, bits):
133 XMLValidator.log.debug('validating boolean: {0}'.format(value))
135 XMLValidator.log.error('value: {0} is larger than expected: {1}'.format(value, 2**bits))
138 def ethernet_check(self, a):
139 XMLValidator.log.debug('validating ethernet address: {0}'.format(a))
140 numbers = a.split(':')
141 max_range = (2**8) - 1
144 if int(n, 16) > max_range:
145 XMLValidator.log.error('octet: {0} in ethernet address: {1} larger than: {2}'.format(n, a, max_range))
148 def mask_check(self, address, mask, base=16, part_len=16, delimiter=':'):
149 if (int(mask) % part_len) != 0:
150 raise StandardError('{0} is not valid mask, should be multiples of {1}'.format(mask, part_len))
152 part_count = int(mask) / part_len
154 for part in address.split(delimiter):
155 part_value = int(part, base) if part != '' else 0
158 if part_count < 0 and part_value != 0:
159 raise StandardError('address part {0} should be 0'.format(part))
161 def ipv4_check(self, a):
162 XMLValidator.log.debug('validating ipv4 address: {0}'.format(a))
164 mask_pos = a.find('/')
166 a_mask = a[mask_pos + 1:]
168 self.mask_check(a, a_mask, 10, 8, '.')
170 numbers = a.split('.')
171 max_range = (2**8) - 1
174 if int(n) > max_range:
175 raise StandardError('octet: {0} in ipv4 address: {1} larger than: {2}'.format(n, a, max_range))
177 def ipv6_check(self, a):
178 XMLValidator.log.debug('validating ipv6 address: {0}'.format(a))
179 mask_pos = a.find('/')
181 a_mask = a[mask_pos + 1:]
183 self.mask_check(a, a_mask)
185 numbers = a.split(':')
186 max_range = (2**16) - 1
189 # if n == '' then the number is 0000 which is always smaller than max_range
190 if n != '' and int(n, 16) > max_range:
191 raise StandardError('number: {0} in ipv6 address: {1} larger than: {2}'.format(n, a, max_range))
193 def check_size(self, value, bits, value_type, convert_from=10):
194 XMLValidator.log.debug('checking value: {0}, size should be {1} bits'.format(value, bits))
195 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})?$")
196 ipv4_regexp = re.compile("^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$")
197 ethernet_regexp = re.compile("^[0-9,A-F,a-f]{2}(:[0-9,A-F,a-f]{2}){5}$")
200 if value_type == type_boolean and value in ['true', 'false']: # boolean values
201 self.boolean_check(value, bits)
202 elif value_type == type_ethernet and ethernet_regexp.match(value): # ethernet address
203 self.ethernet_check(value)
204 elif value_type == type_ipv4 and ipv4_regexp.match(value): # IPV4 address
205 self.ipv4_check(value)
206 elif value_type == type_ipv6 and ipv6_regexp.match(value): # IPV6 address
207 self.ipv6_check(value)
208 elif value_type == type_int: # integer values
209 self.integer_check(value, bits, convert_from)
213 XMLValidator.log.info('size of: {0} < 2^{1} validated successfully'.format(value, bits))
216 XMLValidator.log.error('problem converting value to int or IP addresses: {0}'.format(value))
220 XMLValidator.log.error('problem converting value: {0}, TypeError'.format(value))
223 except StandardError as e:
224 XMLValidator.log.error('problem checking size for value: {0}, reason: {1}'.format(value, str(e)))
227 def has_prerequisite(self, key, values, convert_from, flow_dict):
228 XMLValidator.log.debug('checking prerequisite: {0} - {1}'.format(key, values))
230 flow_value_raw = flow_dict[key]
232 # if prerequisites values are [None] we don't care about actual value
234 flow_value = int(flow_value_raw, convert_from)
236 if flow_value not in values:
237 raise StandardError()
239 XMLValidator.log.info('prerequisite {0}: {1} to value {2} validated successfully'.format(
240 key, values, flow_value_raw))
243 XMLValidator.log.error('can\'t find element: {0} in xml {1} or in keywords {2}'.format(
244 key, self.xml_string, self.mkwd.keys()))
247 except ValueError or TypeError:
248 # flow_value_raw is string that cannot be converted to decimal or hex number or None
249 if flow_value_raw not in values:
250 XMLValidator.log.error(
251 'can\'t find element: {0} with value value: {1} '
252 'in expected values {2}'.format(key, flow_value_raw, values))
255 XMLValidator.log.info('prerequisite {0}: {1} to value {2} validated successfully'.format(
256 key, values, flow_value_raw))
258 except StandardError:
259 XMLValidator.log.error(
260 'can\'t find element: {0} with value value: {1} '
261 'in expected values {2}'.format(key, flow_value, values))
264 def check_all_prerequisites(self, prerequisites_dict, convert_from, flow_dict):
265 XMLValidator.log.debug('checking prerequisites: {0}'.format(prerequisites_dict))
266 for k, v in prerequisites_dict.items():
267 self.has_prerequisite(k, v, convert_from, flow_dict)
269 def check_single_field(self, field, flow_dict):
271 @type field MatchField
275 if field.key not in flow_dict:
276 XMLValidator.log.debug('{0} is not set in XML, skipping validation'.format(field.key))
279 XMLValidator.log.info('validating: {0}'.format(field))
281 if field.bits is not None:
282 self.check_size(flow_dict[field.key], field.bits, field.value_type, field.convert_from)
284 if field.prerequisites is not None:
285 self.check_all_prerequisites(field.prerequisites, field.convert_from, flow_dict)
287 def validate_fields(self):
289 XMLValidator.log.info('validating against flow: {0}'.format(self.flow_dict))
290 for field in self.fields:
291 self.check_single_field(field, self.flow_dict)
293 def validate_misc_values(self):
294 for kw in self.kwd.keys():
295 if kw in self.flow_dict.keys():
296 XMLValidator.log.info('validating: {0}: {1}'.format(kw, self.flow_dict[kw]))
298 value = int(self.flow_dict[kw])
300 XMLValidator.log.error('value: {0}: {1} should be non-negative'.format(kw, self.flow_dict[kw]))
303 XMLValidator.log.info('value: {0}: {1} validated successfully'.format(kw, self.flow_dict[kw]))
304 except StandardError:
305 XMLValidator.log.error('can\'t convert value: {0}: {1} to integer'.format(kw, self.flow_dict[kw]))
308 XMLValidator.log.debug('{0} is not set in XML, skipping validation'.format(kw))
311 self.validate_fields()
312 self.validate_misc_values()
314 XMLValidator.log.info('XML valid: {0}'.format(self.xml_ok))
321 IN_PORT = Field('in-port', 32)
322 IN_PHY_PORT = Field('in-phy-port', 32, {'in-port': [None]})
323 METADATA = Field('metadata', 64, convert_from=16)
325 ETH_DST = Field('ethernet-source', 48, value_type=type_ethernet)
326 ETH_SRC = Field('ethernet-destination', 48, value_type=type_ethernet)
327 ETH_TYPE = Field('ethernet-type', 16)
329 VLAN_VID = Field('vlan-id', 13)
330 VLAN_PCP = Field('vlan-pcp', 3, {'vlan-id': [None]})
332 IP_DSCP = Field('ip-dscp', 6, {'ethernet-type': [2048, 34525]})
333 IP_ENC = Field('ip-ecn', 2, {'ethernet-type': [2048, 34525]})
334 IP_PROTO = Field('ip-protocol', 8, {'ethernet-type': [2048, 34525]})
336 IPV4_SRC = Field('ipv4-source', 32, {'ethernet-type': [2048]}, value_type=type_ipv4)
337 IPV4_DST = Field('ipv4-destination', 32, {'ethernet-type': [2048]}, value_type=type_ipv4)
339 TCP_SRC = Field('tcp-source-port', 16, {'ip-protocol': [6]})
340 TCP_DST = Field('tcp-destination-port', 16, {'ip-protocol': [6]})
341 UDP_SRC = Field('udp-source-port', 16, {'ip-protocol': [17]})
342 UDP_DST = Field('udp-destination-port', 16, {'ip-protocol': [17]})
343 SCTP_SRC = Field('sctp-source-port', 16, {'ip-protocol': [132]})
344 SCTP_DST = Field('sctp-destination-port', 16, {'ip-protocol': [132]})
345 ICMPV4_TYPE = Field('icmpv4-type', 8, {'ip-protocol': [1]})
346 ICMPV4_CODE = Field('icmpv4-code', 8, {'ip-protocol': [1]})
348 ARP_OP = Field('arp-op', 16, {'ethernet-type': [2054]})
349 ARP_SPA = Field('arp-source-transport-address', 32, {'ethernet-type': [2054]}, value_type=type_ipv4)
350 ARP_TPA = Field('arp-target-transport-address', 32, {'ethernet-type': [2054]}, value_type=type_ipv4)
351 ARP_SHA = Field('arp-source-hardware-address', 48, {'ethernet-type': [2054]}, value_type=type_ethernet)
352 ARP_THA = Field('arp-target-hardware-address', 48, {'ethernet-type': [2054]}, value_type=type_ethernet)
354 IPV6_SRC = Field('ipv6-source', 128, {'ethernet-type': [34525]}, value_type=type_ipv6)
355 IPV6_DST = Field('ipv6-destination', 128, {'ethernet-type': [34525]}, value_type=type_ipv6)
356 IPV6_FLABEL = Field('ipv6-flabel', 20, {'ethernet-type': [34525]})
358 ICMPV6_TYPE = Field('icmpv6-type', 8, {'ip-protocol': [58]})
359 ICMPV6_CODE = Field('icmpv6-code', 8, {'ip-protocol': [58]})
361 IPV6_ND_TARGET = Field('ipv6-nd-target', 128, {'icmpv6-type': [135, 136]}, value_type=type_ipv6)
362 IPV6_ND_SLL = Field('ipv6-nd-sll', 48, {'icmpv6-type': [135]}, value_type=type_ethernet)
363 IPV6_ND_TLL = Field('ipv6-nd-tll', 48, {'icmpv6-type': [136]}, value_type=type_ethernet)
365 MPLS_LABEL = Field('mpls-label', 20, {'ethernet-type': [34887, 34888]})
366 MPLS_TC = Field('mpls-tc', 3, {'ethernet-type': [34887, 34888]})
367 MPLS_BOS = Field('mpls-bos', 1, {'ethernet-type': [34887, 34888]})
369 PBB_ISID = Field('pbb-isid', 24, {'ethernet-type': [35047]})
370 TUNNEL_ID = Field('tunnel-id', 64)
371 IPV6_EXTHDR = Field('ipv6-exthdr', 9, {'ethernet-type': [34525]})
374 def fill_validator(validator):
376 @type validator XMLValidator
379 validator.add_field(Matchers.IN_PORT)
380 validator.add_field(Matchers.IN_PHY_PORT)
381 validator.add_field(Matchers.METADATA)
382 validator.add_field(Matchers.ETH_DST)
383 validator.add_field(Matchers.ETH_SRC)
384 validator.add_field(Matchers.ETH_TYPE)
385 # incorrenct XML parsing, if vlan-id-present is present its overriden by it, need to fix loader
386 # validator.add_field(Matchers.VLAN_VID)
387 validator.add_field(Matchers.VLAN_PCP)
388 validator.add_field(Matchers.IP_DSCP)
389 validator.add_field(Matchers.IP_ENC)
390 validator.add_field(Matchers.IP_PROTO)
391 validator.add_field(Matchers.IPV4_SRC)
392 validator.add_field(Matchers.IPV4_DST)
393 validator.add_field(Matchers.TCP_SRC)
394 validator.add_field(Matchers.TCP_DST)
395 validator.add_field(Matchers.UDP_SRC)
396 validator.add_field(Matchers.UDP_DST)
397 validator.add_field(Matchers.SCTP_SRC)
398 validator.add_field(Matchers.SCTP_DST)
399 validator.add_field(Matchers.ICMPV4_TYPE)
400 validator.add_field(Matchers.ICMPV4_CODE)
401 validator.add_field(Matchers.ARP_OP)
402 validator.add_field(Matchers.ARP_SPA)
403 validator.add_field(Matchers.ARP_TPA)
404 validator.add_field(Matchers.ARP_SHA)
405 validator.add_field(Matchers.ARP_THA)
406 validator.add_field(Matchers.IPV6_SRC)
407 validator.add_field(Matchers.IPV6_DST)
408 validator.add_field(Matchers.IPV6_FLABEL)
409 validator.add_field(Matchers.ICMPV6_TYPE)
410 validator.add_field(Matchers.ICMPV6_CODE)
411 validator.add_field(Matchers.IPV6_ND_TARGET)
412 validator.add_field(Matchers.IPV6_ND_SLL)
413 validator.add_field(Matchers.IPV6_ND_TLL)
414 validator.add_field(Matchers.MPLS_LABEL)
415 validator.add_field(Matchers.MPLS_TC)
416 validator.add_field(Matchers.MPLS_BOS)
417 validator.add_field(Matchers.PBB_ISID)
418 validator.add_field(Matchers.TUNNEL_ID)
419 validator.add_field(Matchers.IPV6_EXTHDR)
422 if __name__ == '__main__':
425 with open(os.path.abspath(__file__ + '/../../../keywords.csv')) as f:
426 keywords = dict(line.strip().split(';') for line in f if not line.startswith('#'))
430 match_keywords = None
431 with open(os.path.abspath(__file__ + '/../../../match-keywords.csv')) as f:
432 match_keywords = dict(line.strip().split(';') for line in f if not line.startswith('#'))
434 # print match_keywords
436 action_keywords = None
437 with open(os.path.abspath(__file__ + '/../../../action-keywords.csv')) as f:
438 action_keywords = dict(line.strip().split(';') for line in f if not line.startswith('#'))
440 paths_to_xml = list()
441 for i in range(1, 50):
442 # paths_to_xml = ['xmls/f5.xml', 'xmls/f14.xml', 'xmls/f23.xml', 'xmls/f25.xml']
443 paths_to_xml.append('xmls/f%d.xml' % i)
445 validator = XMLValidator(keywords, action_keywords, match_keywords, logging.ERROR)
446 validator.fill_fields()
448 for path in paths_to_xml:
449 validator.create_dictionaries(path)