-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathioam-agent.py
More file actions
221 lines (182 loc) · 5.18 KB
/
ioam-agent.py
File metadata and controls
221 lines (182 loc) · 5.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import sys
import os
import os.path
import getopt
import socket
import grpc
import ioam_api_pb2
import ioam_api_pb2_grpc
from bitstruct import unpack
ETH_P_IPV6 = 0x86DD
IPV6_TLV_IOAM = 49
IOAM_PREALLOC_TRACE = 0
TRACE_TYPE_BIT0_MASK = 1 << 23 # Hop_Lim + Node Id (short)
TRACE_TYPE_BIT1_MASK = 1 << 22 # Ingress/Egress Ids (short)
TRACE_TYPE_BIT2_MASK = 1 << 21 # Timestamp seconds
TRACE_TYPE_BIT3_MASK = 1 << 20 # Timestamp fraction
TRACE_TYPE_BIT4_MASK = 1 << 19 # Transit Delay
TRACE_TYPE_BIT5_MASK = 1 << 18 # Namespace Data (short)
TRACE_TYPE_BIT6_MASK = 1 << 17 # Queue depth
TRACE_TYPE_BIT7_MASK = 1 << 16 # Checksum Complement
TRACE_TYPE_BIT8_MASK = 1 << 15 # Hop_Lim + Node Id (wide)
TRACE_TYPE_BIT9_MASK = 1 << 14 # Ingress/Egress Ids (wide)
TRACE_TYPE_BIT10_MASK = 1 << 13 # Namespace Data (wide)
TRACE_TYPE_BIT11_MASK = 1 << 12 # Buffer Occupancy
TRACE_TYPE_BIT22_MASK = 1 << 1 # Opaque State Snapshot
def parse_node_data(p, ttype):
node = ioam_api_pb2.IOAMNode()
i = 0
if ttype & TRACE_TYPE_BIT0_MASK:
node.HopLimit, node.Id = unpack(">u8u24", p[i:i+4])
i += 4
if ttype & TRACE_TYPE_BIT1_MASK:
node.IngressId, node.EgressId = unpack(">u16u16", p[i:i+4])
i += 4
if ttype & TRACE_TYPE_BIT2_MASK:
node.TimestampSecs = unpack(">u32", p[i:i+4])[0]
i += 4
if ttype & TRACE_TYPE_BIT3_MASK:
node.TimestampFrac = unpack(">u32", p[i:i+4])[0]
i += 4
if ttype & TRACE_TYPE_BIT4_MASK:
node.TransitDelay = unpack(">u32", p[i:i+4])[0]
i += 4
if ttype & TRACE_TYPE_BIT5_MASK:
node.NamespaceData = unpack(">r32", p[i:i+4])[0]
i += 4
if ttype & TRACE_TYPE_BIT6_MASK:
node.QueueDepth = unpack(">u32", p[i:i+4])[0]
i += 4
if ttype & TRACE_TYPE_BIT7_MASK:
node.CsumComp = unpack(">u32", p[i:i+4])[0]
i += 4
if ttype & TRACE_TYPE_BIT8_MASK:
node.HopLimit, node.IdWide = unpack(">u8u56", p[i:i+8])
i += 8
if ttype & TRACE_TYPE_BIT9_MASK:
node.IngressIdWide, node.EgressIdWide = unpack(">u32u32", p[i:i+8])
i += 8
if ttype & TRACE_TYPE_BIT10_MASK:
node.NamespaceDataWide = unpack(">r64", p[i:i+8])[0]
i += 8
if ttype & TRACE_TYPE_BIT11_MASK:
node.BufferOccupancy = unpack(">u32", p[i:i+4])[0]
i += 4
return node
def parse_ioam_trace(p):
try:
ns, nodelen, _, remlen, ttype = unpack(">u16u5u4u7u24", p[:8])
nodes = []
i = 8 + remlen * 4
while i < len(p):
node = parse_node_data(p[i:i+nodelen*4], ttype)
i += nodelen * 4
if ttype & TRACE_TYPE_BIT22_MASK:
opaque_len, node.OSS.SchemaId = unpack(">u8u24",
p[i:i+4])
if opaque_len > 0:
node.OSS.Data = p[i+4:i+4+opaque_len*4]
i += 4 + opaque_len * 4
nodes.insert(0, node)
trace = ioam_api_pb2.IOAMTrace()
trace.BitField = ttype << 8
trace.NamespaceId = ns
trace.Nodes.extend(nodes)
return trace
except:
return None
def parse(p):
try:
nextHdr = p[6]
if nextHdr != socket.IPPROTO_HOPOPTS:
return None
hbh_len = (p[41] + 1) << 3
i = 42
traces = []
while hbh_len > 0:
opt_type, opt_len = unpack(">u8u8", p[i:i+2])
opt_len += 2
if (opt_type == IPV6_TLV_IOAM and
p[i+3] == IOAM_PREALLOC_TRACE):
trace = parse_ioam_trace(p[i+4:i+opt_len])
if trace is not None:
traces.append(trace)
i += opt_len
hbh_len -= opt_len
return traces
except:
return None
def report_ioam(func, traces):
try:
for trace in traces:
func(trace)
except grpc.RpcError as e:
# TODO IOAM collector is probably not online
pass
def listen(interface, collector):
try:
sock, stub, func = None, None, None
sock = socket.socket(socket.AF_PACKET, socket.SOCK_DGRAM,
socket.htons(ETH_P_IPV6))
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE,
interface.encode())
if collector is None:
func = print
print("[IOAM Agent] Printing IOAM traces...")
else:
channel = grpc.insecure_channel(collector)
stub = ioam_api_pb2_grpc.IOAMServiceStub(channel)
func = stub.Report
print("[IOAM Agent] Reporting to IOAM collector...")
while True:
traces = parse(sock.recv(2048))
if traces is not None and len(traces) > 0:
report_ioam(func, traces)
except KeyboardInterrupt:
print("[IOAM Agent] Closing...")
except Exception as e:
print("[IOAM Agent] Closing on unexpected error: "+ str(e))
finally:
if stub is not None:
channel.close()
if sock is not None:
sock.close()
def help():
print("Syntax: "+ os.path.basename(__file__) +" -i <interface> [-o]")
def help_str(err):
print(err)
help()
def interface_exists(interface):
try:
socket.if_nametoindex(interface)
return True
except OSError:
return False
def main(script, argv):
try:
opts, args = getopt.getopt(argv, "hi:o",
["help", "interface=", "output"])
except getopt.GetoptError:
help()
sys.exit(1)
interface = ""
output = False
for opt, arg in opts:
if opt in ("-h", "--help"):
help()
sys.exit()
if opt in ("-i", "--interface"):
interface = arg
if opt in ("-o", "--output"):
output = True
if not interface_exists(interface):
help_str("Unknown interface "+ interface)
sys.exit(1)
try:
collector = os.environ['IOAM_COLLECTOR'] if not output else None
listen(interface, collector)
except KeyError:
print("IOAM collector is not defined")
sys.exit(1)
if __name__ == "__main__":
main(sys.argv[0], sys.argv[1:])