blob: 935f0e323b769bbd863826b2ae92cc45a3150c05 [file] [log] [blame]
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -05001#!/usr/bin/env python
2# Copyright 2017-present Open Networking Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#--------------------------------------------------------------------------#
16# Copyright (C) 2015 - 2016 by Tibit Communications, Inc. #
17# All rights reserved. #
18# #
19# _______ ____ _ ______ #
20# /_ __(_) __ )(_)_ __/ #
21# / / / / __ / / / / #
22# / / / / /_/ / / / / #
23# /_/ /_/_____/_/ /_/ #
24# #
25#--------------------------------------------------------------------------#
26""" EOAM protocol implementation in scapy """
27
28TIBIT_VERSION_NUMBER = '1.1.4'
29
30import argparse
31import logging
32import time
33from hexdump import hexdump
34from datetime import datetime
35
36
37logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
38from scapy.layers.l2 import Ether, Dot1Q
39from scapy.sendrecv import sendp
40from scapy.fields import PacketField
41from scapy.packet import bind_layers
42from scapy.fields import StrField, X3BytesField
43from scapy.packet import Packet
44from scapy.fields import ByteEnumField, XShortField, XByteField, MACField, \
45 ByteField, BitEnumField, BitField, ShortField
46from scapy.fields import XLongField, StrFixedLenField, XIntField, \
47 FieldLenField, StrLenField, IntField
48
49import fcntl, socket, struct # for get hw address
50
51from EOAM_Layers import EOAM_MULTICAST_ADDRESS, IGMP_MULTICAST_ADDRESS, OAM_ETHERTYPE
52from EOAM_Layers import VENDOR_SPECIFIC_OPCODE, CABLELABS_OUI, TIBIT_OUI
53from EOAM_Layers import RxedOamMsgTypeEnum, RxedOamMsgTypes
54from EOAM_Layers import EOAMPayload, EOAM_EventMsg, EOAM_VendSpecificMsg, EOAM_TibitMsg, EOAM_DpoeMsg, EOAM_OmciMsg
55
56# TODO should remove import *
57from EOAM_TLV import *
58
59ADTRAN_SHORTENED_VSSN = u'4144' # 'AD'
60TIBIT_SHORTENED_VSSN = u'5442' # 'TB'
61
62def get_oam_msg_type(log, frame):
63
64 respType = RxedOamMsgTypeEnum["Unknown"]
65 recv_frame = frame
66
67 if recv_frame.haslayer(EOAMPayload):
68 if recv_frame.haslayer(EOAM_EventMsg):
69 respType = RxedOamMsgTypeEnum["Event Notification"]
70 elif recv_frame.haslayer(EOAM_OmciMsg):
71 respType = RxedOamMsgTypeEnum["OMCI Message"]
72 else:
73 dpoeOpcode = 0x00
74 if recv_frame.haslayer(EOAM_TibitMsg):
75 dpoeOpcode = recv_frame.getlayer(EOAM_TibitMsg).dpoe_opcode;
76 elif recv_frame.haslayer(EOAM_DpoeMsg):
77 dpoeOpcode = recv_frame.getlayer(EOAM_DpoeMsg).dpoe_opcode;
78
79 # Get Response
80 if (dpoeOpcode == DPoEOpcodes["Get Response"]):
81 respType = RxedOamMsgTypeEnum["DPoE Get Response"]
82
83 # Set Response
84 elif (dpoeOpcode == DPoEOpcodes["Set Response"]):
85 respType = RxedOamMsgTypeEnum["DPoE Set Response"]
86
87 # File Transfer ACK
88 elif (dpoeOpcode == DPoEOpcodes["File Transfer"]):
89 respType = RxedOamMsgTypeEnum["DPoE File Transfer"]
90 else:
91 log.info("Unsupported DPoE Opcode {:0>2X}".format(dpoeOpcode))
92 else:
93 log.info("Invalid OAM Header")
94
95 log.info('Received OAM Message - %s' % RxedOamMsgTypes[respType])
96
97 return respType
98
99
100def handle_get_value(log, loadstr, startOfTlvs, queryBranch, queryLeaf):
101 retVal = False;
102 value = 0
103 branch = 0
104 leaf = 0
105 bytesRead = startOfTlvs
106 loadstrlen = len(loadstr)
107
108 while (bytesRead <= loadstrlen):
109 (branch, leaf) = struct.unpack_from('>BH', loadstr, bytesRead)
110
111 if (branch != 0):
112 bytesRead += 3
113 length = struct.unpack_from('>B', loadstr, bytesRead)[0]
114 bytesRead += 1
115
116 if (length == 1):
117 value = struct.unpack_from(">B", loadstr, bytesRead)[0]
118 elif (length == 2):
119 value = struct.unpack_from(">H", loadstr, bytesRead)[0]
120 elif (length == 4):
121 value = struct.unpack_from(">I", loadstr, bytesRead)[0]
122 elif (length == 8):
123 value = struct.unpack_from(">Q", loadstr, bytesRead)[0]
124 else:
125 if (length >= 0x80):
126 log.info('Branch 0x{:0>2X} Leaf 0x{:0>4X} {}'.format(branch, leaf, DPoEVariableResponseEnum[length]))
127 # Set length to zero so bytesRead doesn't get mistakenly incremented below
128 length = 0
129 else:
130 # Attributes with a length of zero are actually 128 bytes long
131 if (length == 0):
132 length = 128;
133 valStr = ">{}s".format(length)
134 value = struct.unpack_from(valStr, loadstr, bytesRead)[0]
135
136 if (length > 0):
137 bytesRead += length
138
139 if (branch != OamBranches["DPoE Object"]):
140 if ( ((queryBranch == 0) and (queryLeaf == 0)) or
141 ((queryBranch == branch) and (queryLeaf == leaf)) ):
142 # Prevent zero-lengthed values from returning success
143 if (length > 0):
144 retVal = True;
145 break
146 else:
147 break
148
149 if (retVal == False):
150 value = 0
151
152 return retVal,bytesRead,value,branch,leaf
153
154
155def get_value_from_msg(log, frame, branch, leaf):
156 retVal = False
157 value = 0
158 recv_frame = frame
159
160 if recv_frame.haslayer(EOAMPayload):
161 payload = recv_frame.payload
162 if hasattr(payload, 'body'):
163 loadstr = payload.body.load
164 # Get a specific TLV value
165 (retVal,bytesRead,value,retbranch,retleaf) = handle_get_value(log, loadstr, 0, branch, leaf)
166 else:
167 log.info('received frame has no payload')
168 else:
169 log.info('Invalid OAM Header')
170 return retVal,value,
171
172def check_set_resp_attrs(log, loadstr, startOfTlvs):
173 retVal = True;
174 branch = 0
175 leaf = 0
176 length = 0
177 bytesRead = startOfTlvs
178 loadstrlen = len(loadstr)
179
180 while (bytesRead <= loadstrlen):
181 (branch, leaf) = struct.unpack_from('>BH', loadstr, bytesRead)
182# print "Branch/Leaf 0x{:0>2X}/0x{:0>4X}".format(branch, leaf)
183
184 if (branch != 0):
185 bytesRead += 3
186 length = struct.unpack_from('>B', loadstr, bytesRead)[0]
187# print "Length: 0x{:0>2X} ({})".format(length,length)
188 bytesRead += 1
189
190 if (length >= 0x80):
191 log.info('Branch 0x{:0>2X} Leaf 0x{:0>4X} {}'.format(branch, leaf, DPoEVariableResponseEnum[length]))
192 if (length > 0x80):
193 retVal = False;
194 break;
195 else:
196 bytesRead += length
197
198 else:
199 break
200
201 return retVal,branch,leaf,length
202
203def check_set_resp(log, frame):
204 rc = False
205 branch = 0
206 leaf = 0
207 status = 0
208 recv_frame = frame
209
210 if recv_frame.haslayer(EOAMPayload):
211 payload = recv_frame.payload
212 if hasattr(payload, 'body'):
213 loadstr = payload.body.load
214 # Get a specific TLV value
215 (rc,branch,leaf,status) = check_set_resp_attrs(log, loadstr, 0)
216 else:
217 log.info('received frame has no payload')
218 else:
219 log.info('Invalid OAM Header')
220 return rc,branch,leaf,status
221
222
223def handle_get_event_context(log, loadstr, startOfTlvs, queryType):
224 retVal = False;
225 value = 0
226 objType = 0
227 bytesRead = startOfTlvs
228 loadstrlen = len(loadstr)
229
230 while (bytesRead <= loadstrlen):
231 objType = struct.unpack_from('>H', loadstr, bytesRead)[0]
232# print "Branch/Leaf 0x{:0>2X}/0x{:0>4X}".format(branch, leaf)
233
234 if (objType != 0):
235 bytesRead += 2
236 length = struct.unpack_from('>B', loadstr, bytesRead)[0]
237# print "Length: 0x{:0>2X} ({})".format(length,length)
238 bytesRead += 1
239
240 if (length == 1):
241 value = struct.unpack_from(">B", loadstr, bytesRead)[0]
242 elif (length == 2):
243 value = struct.unpack_from(">H", loadstr, bytesRead)[0]
244 elif (length == 4):
245 value = struct.unpack_from(">I", loadstr, bytesRead)[0]
246 elif (length == 8):
247 value = struct.unpack_from(">Q", loadstr, bytesRead)[0]
248 else:
249 valStr = ">{}s".format(length)
250 value = struct.unpack_from(valStr, loadstr, bytesRead)[0]
251
252# print "Value: {}".format(value)
253
254 if (length > 0):
255 bytesRead += length
256
257 if ( (queryType == 0) or (queryType == objType) ):
258 # Prevent zero-lengthed values from returning success
259 if (length > 0):
260 retVal = True;
261 break
262 else:
263 break
264
265 if (retVal == False):
266 value = 0
267
268 return retVal,bytesRead,value,objType
269
270
271def handle_tibit_oam_event(log, loadstr):
272 bytesRead = 0
273 loadstrlen = len(loadstr)
274 if loadstrlen > 0:
275 rc = True
276 num_iters = 0
277 bytesRead = 0
278 link_mac = ""
279 msg = ""
280 # Theare are two contexts in a Tibit-specific event - Source & Reference Contexts
281 while(rc == True and num_iters < 2):
282 objType = 0
283 (rc,bytesRead,value,objType) = handle_get_event_context(log, loadstr, bytesRead, objType)
284 if (rc == True):
285 if objType == 0x0001:
286# print "PON Object 0x{:0>4X} Value = {}".format(objType, value)
287 pass
288 elif objType == 0x000A:
289 # This is a Unicast Logical Link context. Determine if this a GPON or EPON link
290 if value[1:5] == "TBIT":
291 #
292 link_mac = ''.join(s.encode('hex') for s in value[1:3])
293 link_mac += ''.join(s.encode('hex') for s in value[5:9])
294 else:
295 link_mac = ''.join(s.encode('hex') for s in value[1:7])
296
297# print "Unicast Logical Link Object 0x{:0>4X} Value = {}".format(objType, link_mac)
298 else:
299 log.info("Object Type 0x{:0>4X} value = {}".format(objType, value))
300 elif (branch != 0):
301 log.error("Object Type 0x{:0>4X} no value".format(objType))
302 num_iters += 1
303
304 # Pull the Event Code and Event Length out of the event
305 (evtCode, evtLen) = struct.unpack_from('>HB', loadstr, bytesRead)
306 bytesRead += 3
307
308# print "Event Code : 0x{:0>4X}".format(evtCode)
309# print "Event Len : 0x{:0>4X}".format(evtLen)
310
311 # Tibit Registration Event
312 if (evtCode == 0x0001):
313 # Handle Registration Status attribute
314 regStatus = struct.unpack_from('>B', loadstr, bytesRead)[0]
315 if regStatus == 1:
316 msg = "Link {} Registered".format(link_mac)
317 else:
318 msg = "Link {} Deregistered".format(link_mac)
319
320 return objType,evtCode,msg
321
322
323def handle_dpoe_oam_event(log, loadstr):
324 bytesRead = 0
325 loadstrlen = len(loadstr)
326 if loadstrlen > 0:
327
328 (evtCode, raised, objType) = struct.unpack_from('>BBH', loadstr, bytesRead)
329 bytesRead += 4
330
331# print "Event Code : 0x{:0>4X}".format(evtCode)
332# print "Event Len : 0x{:0>4X}".format(evtLen)
333
334 if ((loadstrlen - bytesRead) == 2):
335 objInst = struct.unpack_from(">H", loadstr, bytesRead)[0]
336 elif ((loadstrlen - bytesRead) == 4):
337 objInst = struct.unpack_from(">I", loadstr, bytesRead)[0]
338
339 objTypeStr = ObjectContextEnum[objType]
340 evtCodeStr = DPoEEventCodeEnum[evtCode]
341
342 raisedStr = "Raised"
343 if (raised):
344 rasiedStr = "Cleared"
345
346 #print "{} : {} - {} {}".format(objTypeStr, objInst, evtCodeStr, raisedStr)
347 return objType,evtCode,objTypeStr+":"+evtCodeStr
348
349
350def handle_oam_event(log, frame):
351 recv_frame = frame
352 if recv_frame.haslayer(EOAM_EventMsg):
353 now = datetime.now().strftime('%Y-%m-%f %H:%M:%S.%f')
354 event = recv_frame.getlayer(EOAM_EventMsg)
355 if hasattr(event, 'body'):
356 loadstr = event.body.load
357
358 if (event.tlv_type != VENDOR_SPECIFIC_OPCODE):
359 log.error("unexpected tlv_type 0x%x (expected 0xFE)" % event.tlv_type)
360 elif (event.oui == CABLELABS_OUI):
361 log.info("DPoE Event")
362 objType,eventCode,msg = handle_dpoe_oam_event(log, loadstr)
363 elif (event.oui == TIBIT_OUI):
364 log.info("Tibit-specific Event")
365 objType,eventCode,msg = handle_tibit_oam_event(log, loadstr)
366
367 log.info("Description: %s" % msg)
368 log.info("sequence: 0x%04x" % event.sequence)
369 log.info("tlv_type: 0x%x" % event.tlv_type)
370 log.info("length: 0x%x" % event.length)
371 log.info("oui: 0x%06x" % event.oui)
372 log.info("time_stamp: %s" % now)
373 log.info("obj_type: "+hex(objType))
374 log.info("event_code: "+hex(eventCode))
375
376 # TODO - Store the event for future use or generate alarm
377 #event_data = [msg, event.sequence, objType, eventCode, now]
378
379def handle_omci(log, frame):
380 recv_frame = frame
381 if recv_frame.haslayer(EOAM_OmciMsg):
382 omci = recv_frame.getlayer(EOAM_OmciMsg)
383 if hasattr(omci, 'body'):
384 loadstr = omci.body.load
385
386 #log.info("trans_id: 0x%04x" % omci.trans_id)
387 #log.info("msg_type: 0x%x" % omci.msg_type)
388 #log.info("dev_id: 0x%x" % omci.dev_id)
389 #log.info("me_class: 0x%04x" % omci.me_class)
390 #log.info("me_inst: 0x%04x" % omci.me_inst)
391
392 bytesRead = 0
393
394 # TODO - Handle OMCI message
395
396def handle_fx_ack(log, loadstr):
397 response_code = Dpoe_FileAckRspOpcodes["OK"]
398
399 (fx_opcode, acked_block, response_code) = struct.unpack('>BHB', loadstr[0:4])
400
401 if (fx_opcode == Dpoe_FileXferOpcodes["File Transfer Ack"]):
402 pass
403 #log.info(" Acked_block: {} Code: {}".format(acked_block, DPoEFileAckRespCodeEnum[response_code]))
404 else:
405 log.error("Unexpected File Transfer Opcode {} when expecting ACK".format(DPoEFileXferOpcodeEnum[fx_opcode]))
406
407 return response_code,acked_block
408
409
410def check_resp(log, frame):
411 respType = RxedOamMsgTypeEnum["Unknown"]
412 recv_frame = frame
413 if recv_frame.haslayer(EOAMPayload):
414
415 if recv_frame.haslayer(EOAM_EventMsg):
416 handle_oam_event(log, recv_frame)
417 elif recv_frame.haslayer(EOAM_OmciMsg):
418 handle_omci(log, recv_frame)
419 else:
420 dpoeOpcode = 0x00
421 if recv_frame.haslayer(EOAM_TibitMsg):
422 dpoeOpcode = recv_frame.getlayer(EOAM_TibitMsg).dpoe_opcode;
423 elif recv_frame.haslayer(EOAM_DpoeMsg):
424 dpoeOpcode = recv_frame.getlayer(EOAM_DpoeMsg).dpoe_opcode;
425
426 if hasattr(recv_frame, 'body'):
427 payload = recv_frame.payload
428 loadstr = payload.body.load
429
430 # Get Response
431 if (dpoeOpcode == DPoEOpcodes["Get Response"]):
432 bytesRead = 0
433 rc = True
434 while(rc == True):
435 branch = 0
436 leaf = 0
437 (rc,bytesRead,value,branch,leaf) = handle_get_value(log, loadstr, bytesRead, branch, leaf)
438 if (rc == True):
439 log.info('Branch 0x{:0>2X} Leaf 0x{:0>4X} value = {}'.format(branch, leaf, value))
440 elif (branch != 0):
441 log.info('Branch 0x{:0>2X} Leaf 0x{:0>4X} no value'.format(branch, leaf))
442
443 # Set Response
444 elif (dpoeOpcode == DPoEOpcodes["Set Response"]):
445 (rc,branch,leaf,status) = check_set_resp_attrs(loadstr, 0)
446 if (rc == True):
447 log.info('Set Response had no errors')
448 else:
449 log.info('Branch 0x{:X} Leaf 0x{:0>4X} {}'.format(branch, leaf, DPoEVariableResponseEnum[status]))
450
451 # File Transfer ACK
452 elif (dpoeOpcode == DPoEOpcodes["File Transfer"]):
453 (rc,block) = handle_fx_ack(log, loadstr)
454 else:
455 log.info('Unsupported DPoE Opcode {:0>2X}'.format(dpoeOpcode))
456 else:
457 log.info('Invalid OAM Header')
458
459 return respType
460
461
462def mcastIp2McastMac(ip):
463 """ Convert a dot-notated IPv4 multicast address string into an multicast MAC address"""
464 digits = [int(d) for d in ip.split('.')]
465 return '01:00:5e:%02x:%02x:%02x' % (digits[1] & 0x7f, digits[2] & 0xff, digits[3] & 0xff)
466
467def get_olt_queue(mac, mode = None):
468 resultOltQueue = ""
469 if mode:
470 # If the MAC is the Multicast LLID, then use EPON encoding regardless of the actual
471 # mode we are in.
472 if (mac == "FFFFFFFFFFFF"):
473 mode = "EPON"
474
475 if mode.upper()[0] == "G": #GPON
476 if mac[:4].upper() == ADTRAN_SHORTENED_VSSN:
477 vssn = "ADTN"
478 else:
479 vssn = "TBIT"
480 link = int(mac[4:12], 16)
481 resultOltQueue = "PortIngressRuleResultOLTQueue(unicastvssn=\"" + vssn + "\", unicastlink=" + str(link) + ")"
482 else: #EPON
483 vssn = int(mac[0:8].rjust(8,"0"), 16)
484 link = int((mac[8:12]).ljust(8,"0"), 16)
485 resultOltQueue = "PortIngressRuleResultOLTEPONQueue(unicastvssn=" + str(vssn) + ", unicastlink=" + str(link) + ")"
486 return resultOltQueue
487
488
489def get_unicast_logical_link(mac, mode = None):
490 unicastLogicalLink = ""
491 if mode:
492 if mode.upper()[0] == "G": #GPON
493 if mac[:4].upper() == ADTRAN_SHORTENED_VSSN:
494 vssn = "ADTN"
495 else:
496 vssn = "TBIT"
497 link = int(mac[4:12], 16)
498 unicastLogicalLink = "OLTUnicastLogicalLink(unicastvssn=\"" + vssn + "\", unicastlink=" + str(link) + ")"
499 else: #EPON
500 vssn = int(mac[0:8].rjust(8,"0"), 16)
501 link = int((mac[8:12]).ljust(8,"0"), 16)
502 unicastLogicalLink = "OLTEPONUnicastLogicalLink(unicastvssn=" + str(vssn) + ", unicastlink=" + str(link) +")"
503 return unicastLogicalLink
504
505
506if __name__ == "__main__":
507 pass