4 from xml.etree import ElementTree as ET
9 def loadXml(file_name):
10 path_to_xml = os.path.join('', file_name)
11 with open(path_to_xml) as f:
13 xml_string = re.sub(' xmlns="[^"]+"', '', xml_string, count=1)
15 tree = ET.fromstring(xml_string)
16 return tree, xml_string
19 def buildXmlDocDictionaryForComarableElements(element, flow_dict, p_elm_name=None, kwd=None, akwd=None, mkwd=None):
20 act_key_dict = kwd if (kwd > None) else akwd if (akwd > None) else mkwd if (mkwd > None) else None
22 elm_alias = element.tag if (act_key_dict.get(element.tag, None) > None) else None
23 if ((element.getchildren() > None) & (len(element.getchildren()) > 0)):
24 for child in element.getchildren() :
25 if (element.tag == 'match') :
26 Loader.buildXmlDocDictionaryForComarableElements(child, flow_dict, mkwd=mkwd)
27 elif (element.tag == 'actions') :
28 Loader.buildXmlDocDictionaryForComarableElements(child, flow_dict, akwd=akwd)
30 Loader.buildXmlDocDictionaryForComarableElements(child, flow_dict, elm_alias, kwd, akwd, mkwd);
32 if element.text > None :
33 text = re.sub( '[\s]+','', element.text, count=1)
34 a_key = p_elm_name if (p_elm_name > None) else element.tag
35 flow_dict[a_key] = text;
46 fields to check, arguments:
47 key: element tag from keywords and xml
48 bits: expected length in bits
49 prerequisites: dictionary of elements tag from xml which are required for this field and their values in list
50 or [None] if value is undefined or it's irrelevant (we just need to check if tag is set)
51 convert_from: format in which is value, that is checked against prerequisite values stored in xml
56 prerequisites: {'ethernet-type': [2048]}
59 OF_IPV4_SRC = Field('ipv4-source', 32, {'ethernet-type': [2048]}, 10)
60 IN_PHY_PORT = Field('in-phy-port', 32, {'in-port': [None]}, 10)
63 def __init__(self, key, bits, prerequisites=None, convert_from=10, value_type=type_int):
66 if prerequisites is not None:
67 self.prerequisites = dict(prerequisites)
69 self.prerequisites = None
70 self.convert_from = convert_from
71 self.value_type = value_type
74 return "Field: {}, size: {}, prerequisites: {}"\
75 .format(self.key, self.bits, self.prerequisites)
80 log = logging.getLogger('XMLValidator')
82 channel = logging.StreamHandler()
83 log.addHandler(channel)
85 def __init__(self, kwd, akwd, mkwd, loglevel=logging.INFO):
87 self.test_name = 'No test loaded'
88 XMLValidator.log.setLevel(loglevel)
92 self.flow_dict = dict()
98 def create_dictionaries(self, file_name):
99 self.test_name = file_name
101 formatter = logging.Formatter('TEST {}: %(levelname)s: %(message)s'.format(self.test_name))
102 XMLValidator.channel.setFormatter(formatter)
104 self.flow_dict = dict()
105 treeXml1, self.xml_string = Loader.loadXml(file_name)
106 Loader.buildXmlDocDictionaryForComarableElements(treeXml1, self.flow_dict, kwd=self.kwd, akwd=self.akwd, mkwd=self.mkwd)
107 XMLValidator.log.debug('loaded dict from xml: {}'.format(self.flow_dict))
110 def fill_fields(self):
111 Matchers.fill_validator(self)
113 def add_field(self, fields):
114 self.fields.append(fields)
116 def integer_check(self, value, bits, convert_from=10):
117 XMLValidator.log.debug('validating integer: {}'.format(value))
118 if (int(value, convert_from) / 2**bits) > 0:
119 XMLValidator.log.error('value: {} is larger than expected: {}'.format(value, 2**bits))
122 def boolean_check(self, value, bits):
123 XMLValidator.log.debug('validating boolean: {}'.format(value))
125 XMLValidator.log.error('value: {} is larger than expected: {}'.format(value, 2**bits))
128 def ethernet_check(self, a):
129 XMLValidator.log.debug('validating ethernet address: {}'.format(a))
130 numbers = a.split(':')
131 max_range = (2**8) - 1
134 if int(n, 16) > max_range:
135 XMLValidator.log.error('octet: {} in ethernet address: {} larger than: {}'.format(n, a, max_range))
138 def mask_check(self, address, mask, base=16, part_len=16, delimiter=':'):
139 if (int(mask) % part_len) != 0:
140 raise StandardError('{} is not valid mask, should be multiples of {}'.format(mask, part_len))
142 part_count = int(mask) / part_len
144 for part in address.split(delimiter):
145 part_value = int(part, base) if part != '' else 0
148 if part_count < 0 and part_value != 0:
149 raise StandardError('address part {} should be 0'.format(part))
151 def ipv4_check(self, a):
152 XMLValidator.log.debug('validating ipv4 address: {}'.format(a))
154 mask_pos = a.find('/')
156 a_mask = a[mask_pos + 1:]
158 self.mask_check(a, a_mask, 10, 8, '.')
160 numbers = a.split('.')
161 max_range = (2**8) - 1
164 if int(n) > max_range:
165 raise StandardError('octet: {} in ipv4 address: {} larger than: {}'.format(n, a, max_range))
167 def ipv6_check(self, a):
168 XMLValidator.log.debug('validating ipv6 address: {}'.format(a))
169 mask_pos = a.find('/')
171 a_mask = a[mask_pos + 1:]
173 self.mask_check(a, a_mask)
175 numbers = a.split(':')
176 max_range = (2**16) - 1
179 #if n == '' then the number is 0000 which is always smaller than max_range
180 if n != '' and int(n, 16) > max_range:
181 raise StandardError('number: {} in ipv6 address: {} larger than: {}'.format(n, a, max_range))
183 def check_size(self, value, bits, value_type, convert_from=10):
184 XMLValidator.log.debug('checking value: {}, size should be {} bits'.format(value, bits))
185 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})?$")
186 ipv4_regexp = re.compile("^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$")
187 ethernet_regexp = re.compile("^[0-9,A-F,a-f]{2}(:[0-9,A-F,a-f]{2}){5}$")
190 if value_type == type_boolean and value in ['true', 'false']: #boolean values
191 self.boolean_check(value, bits)
192 elif value_type == type_ethernet and ethernet_regexp.match(value): #ethernet address
193 self.ethernet_check(value)
194 elif value_type == type_ipv4 and ipv4_regexp.match(value): #IPV4 address
195 self.ipv4_check(value)
196 elif value_type == type_ipv6 and ipv6_regexp.match(value): #IPV6 address
197 self.ipv6_check(value)
198 elif value_type == type_int: #integer values
199 self.integer_check(value, bits, convert_from)
203 XMLValidator.log.info('size of: {} < 2^{} validated successfully'.format(value, bits))
206 XMLValidator.log.error('problem converting value to int or IP addresses: {}'.format(value))
210 XMLValidator.log.error('problem converting value: {}, TypeError'.format(value))
213 except StandardError as e:
214 XMLValidator.log.error('problem checking size for value: {}, reason: {}'.format(value, str(e)))
218 def has_prerequisite(self, key, values, convert_from, flow_dict):
219 XMLValidator.log.debug('checking prerequisite: {} - {}'.format(key, values))
221 flow_value_raw = flow_dict[key]
223 #if prerequisites values are [None] we don't care about actual value
225 flow_value = int(flow_value_raw, convert_from)
227 if flow_value not in values:
228 raise StandardError()
230 XMLValidator.log.info('prerequisite {}: {} to value {} validated successfully'.format(key, values, flow_value_raw))
233 XMLValidator.log.error('can\'t find element: {} in xml {} or in keywords {}'.format(key, self.xml_string, self.mkwd.keys()))
236 except ValueError or TypeError:
237 # flow_value_raw is string that cannot be converted to decimal or hex number or None
238 if flow_value_raw not in values:
239 XMLValidator.log.error('can\'t find element: {} with value value: {} '
240 'in expected values {}'.format(key, flow_value_raw, values))
243 XMLValidator.log.info('prerequisite {}: {} to value {} validated successfully'.format(key, values, flow_value_raw))
245 except StandardError:
246 XMLValidator.log.error('can\'t find element: {} with value value: {} '
247 'in expected values {}'.format(key, flow_value, values))
250 def check_all_prerequisites(self, prerequisites_dict, convert_from, flow_dict):
251 XMLValidator.log.debug('checking prerequisites: {}'.format(prerequisites_dict))
252 for k, v in prerequisites_dict.items():
253 self.has_prerequisite(k, v, convert_from, flow_dict)
255 def check_single_field(self, field, flow_dict):
257 @type field MatchField
261 if field.key not in flow_dict:
262 XMLValidator.log.debug('{} is not set in XML, skipping validation'.format(field.key))
265 XMLValidator.log.info('validating: {}'.format(field))
267 if field.bits is not None:
268 self.check_size(flow_dict[field.key], field.bits, field.value_type, field.convert_from)
270 if field.prerequisites is not None:
271 self.check_all_prerequisites(field.prerequisites, field.convert_from, flow_dict)
273 def validate_fields(self):
275 XMLValidator.log.info('validating against flow: {}'.format(self.flow_dict))
276 for field in self.fields:
277 self.check_single_field(field, self.flow_dict)
279 def validate_misc_values(self):
280 for kw in self.kwd.keys():
281 if kw in self.flow_dict.keys():
282 XMLValidator.log.info('validating: {}: {}'.format(kw, self.flow_dict[kw]))
284 value = int(self.flow_dict[kw])
286 XMLValidator.log.error('value: {}: {} should be non-negative'.format(kw, self.flow_dict[kw]))
289 XMLValidator.log.info('value: {}: {} validated successfully'.format(kw, self.flow_dict[kw]))
290 except StandardError:
291 XMLValidator.log.error('can\'t convert value: {}: {} to integer'.format(kw, self.flow_dict[kw]))
294 XMLValidator.log.debug('{} is not set in XML, skipping validation'.format(kw))
297 self.validate_fields()
298 self.validate_misc_values()
300 XMLValidator.log.info('XML valid: {}'.format(self.xml_ok))
306 IN_PORT = Field('in-port', 32)
307 IN_PHY_PORT = Field('in-phy-port', 32, {'in-port': [None]})
308 METADATA = Field('metadata', 64, convert_from=16)
310 ETH_DST = Field('ethernet-source', 48, value_type=type_ethernet)
311 ETH_SRC = Field('ethernet-destination', 48, value_type=type_ethernet)
312 ETH_TYPE = Field('ethernet-type', 16)
314 VLAN_VID = Field('vlan-id', 13)
315 VLAN_PCP = Field('vlan-pcp', 3, {'vlan-id': [None]})
317 IP_DSCP = Field('ip-dscp', 6, {'ethernet-type': [2048, 34525]})
318 IP_ENC = Field('ip-ecn', 2, {'ethernet-type': [2048, 34525]})
319 IP_PROTO = Field('ip-protocol', 8, {'ethernet-type': [2048, 34525]})
321 IPV4_SRC = Field('ipv4-source', 32, {'ethernet-type': [2048]}, value_type=type_ipv4)
322 IPV4_DST = Field('ipv4-destination', 32, {'ethernet-type': [2048]}, value_type=type_ipv4)
324 TCP_SRC = Field('tcp-source-port', 16, {'ip-protocol': [6]})
325 TCP_DST = Field('tcp-destination-port', 16, {'ip-protocol': [6]})
326 UDP_SRC = Field('udp-source-port', 16, {'ip-protocol': [17]})
327 UDP_DST = Field('udp-destination-port', 16, {'ip-protocol': [17]})
328 SCTP_SRC = Field('sctp-source-port', 16, {'ip-protocol': [132]})
329 SCTP_DST = Field('sctp-destination-port', 16, {'ip-protocol': [132]})
330 ICMPV4_TYPE = Field('icmpv4-type', 8, {'ip-protocol': [1]})
331 ICMPV4_CODE = Field('icmpv4-code', 8, {'ip-protocol': [1]})
333 ARP_OP = Field('arp-op', 16, {'ethernet-type': [2054]})
334 ARP_SPA = Field('arp-source-transport-address', 32, {'ethernet-type': [2054]}, value_type=type_ipv4)
335 ARP_TPA = Field('arp-target-transport-address', 32, {'ethernet-type': [2054]}, value_type=type_ipv4)
336 ARP_SHA = Field('arp-source-hardware-address', 48, {'ethernet-type': [2054]}, value_type=type_ethernet)
337 ARP_THA = Field('arp-target-hardware-address', 48, {'ethernet-type': [2054]}, value_type=type_ethernet)
339 IPV6_SRC = Field('ipv6-source', 128, {'ethernet-type': [34525]}, value_type=type_ipv6)
340 IPV6_DST = Field('ipv6-destination', 128, {'ethernet-type': [34525]}, value_type=type_ipv6)
341 IPV6_FLABEL = Field('ipv6-flabel', 20, {'ethernet-type': [34525]})
343 ICMPV6_TYPE = Field('icmpv6-type', 8, {'ip-protocol': [58]})
344 ICMPV6_CODE = Field('icmpv6-code', 8, {'ip-protocol': [58]})
346 IPV6_ND_TARGET = Field('ipv6-nd-target', 128, {'icmpv6-type': [135, 136]}, value_type=type_ipv6)
347 IPV6_ND_SLL = Field('ipv6-nd-sll', 48, {'icmpv6-type': [135]}, value_type=type_ethernet)
348 IPV6_ND_TLL = Field('ipv6-nd-tll', 48, {'icmpv6-type': [136]}, value_type=type_ethernet)
350 MPLS_LABEL = Field('mpls-label', 20, {'ethernet-type': [34887, 34888]})
351 MPLS_TC = Field('mpls-tc', 3, {'ethernet-type': [34887, 34888]})
352 MPLS_BOS = Field('mpls-bos', 1, {'ethernet-type': [34887, 34888]})
354 PBB_ISID = Field('pbb-isid', 24, {'ethernet-type': [35047]})
355 TUNNEL_ID = Field('tunnel-id', 64)
356 IPV6_EXTHDR = Field('ipv6-exthdr', 9, {'ethernet-type': [34525]})
360 def fill_validator(validator):
362 @type validator XMLValidator
365 validator.add_field(Matchers.IN_PORT)
366 validator.add_field(Matchers.IN_PHY_PORT)
367 validator.add_field(Matchers.METADATA)
368 validator.add_field(Matchers.ETH_DST)
369 validator.add_field(Matchers.ETH_SRC)
370 validator.add_field(Matchers.ETH_TYPE)
371 #validator.add_field(Matchers.VLAN_VID) - incorrenct XML parsing, if vlan-id-present is present its overriden by it, need to fix loader
372 validator.add_field(Matchers.VLAN_PCP)
373 validator.add_field(Matchers.IP_DSCP)
374 validator.add_field(Matchers.IP_ENC)
375 validator.add_field(Matchers.IP_PROTO)
376 validator.add_field(Matchers.IPV4_SRC)
377 validator.add_field(Matchers.IPV4_DST)
378 validator.add_field(Matchers.TCP_SRC)
379 validator.add_field(Matchers.TCP_DST)
380 validator.add_field(Matchers.UDP_SRC)
381 validator.add_field(Matchers.UDP_DST)
382 validator.add_field(Matchers.SCTP_SRC)
383 validator.add_field(Matchers.SCTP_DST)
384 validator.add_field(Matchers.ICMPV4_TYPE)
385 validator.add_field(Matchers.ICMPV4_CODE)
386 validator.add_field(Matchers.ARP_OP)
387 validator.add_field(Matchers.ARP_SPA)
388 validator.add_field(Matchers.ARP_TPA)
389 validator.add_field(Matchers.ARP_SHA)
390 validator.add_field(Matchers.ARP_THA)
391 validator.add_field(Matchers.IPV6_SRC)
392 validator.add_field(Matchers.IPV6_DST)
393 validator.add_field(Matchers.IPV6_FLABEL)
394 validator.add_field(Matchers.ICMPV6_TYPE)
395 validator.add_field(Matchers.ICMPV6_CODE)
396 validator.add_field(Matchers.IPV6_ND_TARGET)
397 validator.add_field(Matchers.IPV6_ND_SLL)
398 validator.add_field(Matchers.IPV6_ND_TLL)
399 validator.add_field(Matchers.MPLS_LABEL)
400 validator.add_field(Matchers.MPLS_TC)
401 validator.add_field(Matchers.MPLS_BOS)
402 validator.add_field(Matchers.PBB_ISID)
403 validator.add_field(Matchers.TUNNEL_ID)
404 validator.add_field(Matchers.IPV6_EXTHDR)
407 if __name__ == '__main__':
410 with open('keywords.csv') as f:
411 keywords = dict(line.strip().split(';') for line in f if not line.startswith('#'))
415 match_keywords = None
416 with open('match-keywords.csv') as f:
417 match_keywords = dict(line.strip().split(';') for line in f if not line.startswith('#'))
419 #print match_keywords
421 action_keywords = None
422 with open('action-keywords.csv') as f:
423 action_keywords = dict(line.strip().split(';') for line in f if not line.startswith('#'))
425 paths_to_xml = list()
426 for i in range(1, 50):
427 #paths_to_xml = ['xmls/f5.xml', 'xmls/f14.xml', 'xmls/f23.xml', 'xmls/f25.xml']
428 paths_to_xml.append('xmls/f%d.xml' % i)
430 validator = XMLValidator(keywords, action_keywords, match_keywords, logging.ERROR)
431 validator.fill_fields()
433 for path in paths_to_xml:
434 validator.create_dictionaries(path)