umnetdb-utils 0.1.4__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
umnetdb_utils/utils.py CHANGED
@@ -1,6 +1,26 @@
1
1
  import ipaddress
2
2
  import re
3
- def is_ip_address(input_str, version=None):
3
+
4
+ from dataclasses import dataclass, field
5
+ from copy import copy, deepcopy
6
+
7
+ from typing import Optional, Union, List, Dict
8
+
9
+ type IPAddress = Union[ipaddress.IPv4Address, ipaddress.IPv6Address]
10
+
11
+ LOCAL_PROTOCOLS = [
12
+ "Access-internal",
13
+ "am",
14
+ "connected",
15
+ "direct",
16
+ "Direct",
17
+ "hmm",
18
+ "local",
19
+ "VPN",
20
+ "vrrpv3",
21
+ ]
22
+
23
+ def is_ip_address(input_str:str, version:Optional[int]=None):
4
24
  try:
5
25
  ip = ipaddress.ip_address(input_str)
6
26
  except ValueError:
@@ -11,8 +31,8 @@ def is_ip_address(input_str, version=None):
11
31
 
12
32
  return True
13
33
 
14
- def is_ip_network(input_str, version=None):
15
-
34
+
35
+ def is_ip_network(input_str:str, version:Optional[int]=None):
16
36
  # First check that this is a valid IP or network
17
37
  try:
18
38
  net = ipaddress.ip_network(input_str)
@@ -21,19 +41,283 @@ def is_ip_network(input_str, version=None):
21
41
 
22
42
  if version and version != net.version:
23
43
  return False
24
-
44
+
25
45
  return True
26
46
 
27
- def is_mac_address(input_str):
28
- '''
47
+
48
+ def is_mac_address(input_str:str):
49
+ """
29
50
  Validates the input string as a mac address. Valid formats are
30
51
  XX:XX:XX:XX:XX:XX, XX-XX-XX-XX-XX-XX, XXXX.XXXX.XXXX
31
52
  where 'X' is a hexadecimal digit (upper or lowercase).
32
- '''
53
+ """
33
54
  mac = input_str.lower()
34
- if re.match(r'[0-9a-f]{2}([-:])[0-9a-f]{2}(\1[0-9a-f]{2}){4}$', mac):
55
+ if re.match(r"[0-9a-f]{2}([-:])[0-9a-f]{2}(\1[0-9a-f]{2}){4}$", mac):
35
56
  return True
36
- if re.match(r'[0-9a-f]{4}\.[0-9a-f]{4}\.[0-9a-f]{4}$', mac):
57
+ if re.match(r"[0-9a-f]{4}\.[0-9a-f]{4}\.[0-9a-f]{4}$", mac):
37
58
  return True
38
59
 
39
60
  return False
61
+
62
+
63
+ @dataclass(order=True)
64
+ class Packet:
65
+ """
66
+ Modeling a packet as it traverses the network
67
+ """
68
+
69
+ dst_ip: IPAddress
70
+ #ttl: int = 255
71
+ inner_dst_ip: Optional[IPAddress] = None
72
+ label_stack: Optional[list[int]] = None
73
+ vni: Optional[int] = None
74
+
75
+ def __str__(self):
76
+ if self.label_stack:
77
+ return f"MPLS: {self.label_stack}"
78
+ if self.vni:
79
+ return f"VXLAN: {self.dst_ip}:{self.vni}"
80
+ return f"{self.dst_ip}"
81
+
82
+ # def decrement_ttl(self):
83
+ # new_packet = copy(self)
84
+ # new_packet.ttl -= 1
85
+ # return new_packet
86
+
87
+ def vxlan_encap(self, vni:int, tunnel_destination: ipaddress.IPv4Address) -> "Packet":
88
+ """
89
+ Return a copy of the existing packet, but with a vxlan encap
90
+ """
91
+ if self.vni or self.inner_dst_ip or self.label_stack:
92
+ raise ValueError(f"Can't encapsulate an already-encapsulated packet: {self}")
93
+
94
+ # new_packet = deepcopy(self)
95
+ # new_packet.inner_dst_ip = self.dst_ip
96
+ # new_packet.vni = vni
97
+ # new_packet.dst_ip = tunnel_destination
98
+
99
+ # return new_packet
100
+ self.inner_dst_ip = self.dst_ip
101
+ self.vni = vni
102
+ self.dst_ip = tunnel_destination
103
+
104
+ def vxlan_decap(self) -> "Packet":
105
+ """
106
+ Return a copy of the existing packet, but with a vxlan decap
107
+ """
108
+ if not self.vni or not self.inner_dst_ip or self.label_stack:
109
+ raise ValueError(f"Can't decap a non-VXLAN packet: {self}")
110
+
111
+ # new_packet = deepcopy(self)
112
+ # new_packet.dst_ip = self.inner_dst_ip
113
+ # new_packet.vni = None
114
+ # new_packet.inner_dst_ip = None
115
+
116
+ # return new_packet
117
+ self.dst_ip = self.inner_dst_ip
118
+ self.vni = None
119
+ self.inner_dst_ip = None
120
+
121
+ def mpls_push(self, label:Union[int,list[int]]) -> "Packet":
122
+ """
123
+ Retrun a copy of the existing packet but with MPLS labels pushed on the stack
124
+ """
125
+ if self.vni or self.inner_dst_ip:
126
+ raise ValueError(f"Can't do mpls on a VXLAN packet: {self}")
127
+
128
+ # new_packet = deepcopy(self)
129
+ # if not new_packet.label_stack:
130
+ # new_packet.label_stack = []
131
+
132
+ # if isinstance(label, int):
133
+ # new_packet.label_stack.append(label)
134
+ # else:
135
+ # new_packet.label_stack.extend(label)
136
+
137
+ # return new_packet
138
+ if not self.label_stack:
139
+ self.label_stack = []
140
+
141
+ if isinstance(label, list):
142
+ self.label_stack.extend(label)
143
+ else:
144
+ self.label_stack.append(label)
145
+
146
+ def mpls_pop(self, num_pops:int=1) -> "Packet":
147
+ """
148
+ Return a copy of the existing packet but with MPLS label(s) popped
149
+ """
150
+ if not self.label_stack:
151
+ raise ValueError(f"Can't pop from an empty label stack!: {self}")
152
+ if len(self.label_stack) < num_pops:
153
+ raise ValueError(f"Can't pop {num_pops} labels from packet: {self}")
154
+
155
+ # new_packet = copy(self)
156
+ # for _ in range(num_pops):
157
+ # new_packet.label_stack.pop()
158
+
159
+ # return new_packet
160
+
161
+ for _ in range(num_pops):
162
+ self.label_stack.pop()
163
+
164
+
165
+ def mpls_swap(self, label:Union[int,list[int]]) -> "Packet":
166
+ """
167
+ Rerturn a copy of the existing packet but with MPLS label swap
168
+ """
169
+ if not self.label_stack:
170
+ raise ValueError(f"Can't pop from an empty label stack!: {self}")
171
+
172
+ # new_packet = copy(self)
173
+ # new_packet.label_stack.pop()
174
+ # if isinstance(label, int):
175
+ # new_packet.label_stack.append(label)
176
+
177
+ # # inconsistency in mpls table where this really should be a 'pop' but
178
+ # # it shows up as a single blank entry in a list.
179
+ # elif label == [""]:
180
+ # new_packet.label_stack = []
181
+ # else:
182
+ # new_packet.label_stack.extend(label)
183
+
184
+ # return new_packet
185
+ self.label_stack.pop()
186
+ if label == [""]:
187
+ self.lable_stack = []
188
+ elif isinstance(label, list):
189
+ self.label_stack.extend(label)
190
+ else:
191
+ self.label_stack.append(label)
192
+
193
+ def is_encapped(self):
194
+ return bool(self.vni or self.label_stack)
195
+
196
+
197
+ @dataclass(order=True)
198
+ class Hop:
199
+ """
200
+ A hop along the path and the corresponding packet at that location
201
+ """
202
+ router: str
203
+ vrf: str
204
+ interface: str
205
+ packet: Packet
206
+
207
+ _parent: "Hop" = field(init=False, compare=False, default=None)
208
+ _children: List["Hop"] = field(init=False, compare=False, default_factory=list)
209
+
210
+ def __str__(self):
211
+ vrf = "" if self.vrf == "default" else f" VRF {self.vrf}"
212
+ return f"{self.router}{vrf} {self.interface}, Packet: {self.packet}"
213
+
214
+ def copy(self):
215
+ return deepcopy(self)
216
+
217
+ @classmethod
218
+ def unknown(self, packet=Packet):
219
+ """
220
+ Returns an "unknown" hop for a specific packet
221
+ """
222
+ return Hop("","","", packet)
223
+
224
+ def is_unknown(self):
225
+ return self.router == ""
226
+
227
+ def __hash__(self):
228
+ return(hash(str(self)))
229
+
230
+ def add_next_hop(self, other:"Hop"):
231
+ """
232
+ creates parent/child relationship between this hop and another one
233
+ """
234
+ self._children.append(other)
235
+ other._parent = self
236
+
237
+
238
+
239
+ class Path:
240
+ """
241
+ Keeps track of all our paths
242
+ """
243
+ def __init__(self, first_hop:Hop):
244
+
245
+ self.first_hop = first_hop
246
+ self.hops = {str(first_hop): first_hop}
247
+
248
+ def add_hop(self, curr_hop:Hop, next_hop:Hop) -> bool:
249
+ """
250
+ Adds a hop to our current hop. If this is a new hop,
251
+ which means we're on a new path, returns true. Otherwise
252
+ returns false to indicate we don't have to walk this path
253
+ since it's a duplicate of another.
254
+ """
255
+
256
+ # if we've seen this hop already we will check for loops.
257
+ if str(next_hop) in self.hops:
258
+ new_path = False
259
+
260
+ # parent = self.curr_hop._parent
261
+ # while parent:
262
+ # if parent == next_hop:
263
+ # raise ValueError(f"Loop detected for {self.curr_hop} -> {next_hop}")
264
+ # parent = parent._parent
265
+
266
+ self.hops[str(next_hop)]
267
+
268
+ else:
269
+ new_path = True
270
+ self.hops[str(next_hop)] = next_hop
271
+
272
+ curr_hop.add_next_hop(next_hop)
273
+ return new_path
274
+
275
+
276
+
277
+ def walk_path(self) -> Dict[int, List[Hop]]:
278
+ """
279
+ 'Flattens' path from first hop to last into a dict
280
+ of list of hops keyed on hop number.
281
+ """
282
+ hop_num = 1
283
+ result = { hop_num: {self.first_hop}}
284
+ self._walk_path(self.first_hop, hop_num, result)
285
+ return result
286
+
287
+
288
+ def _walk_path(self, curr_hop, hop_num, result):
289
+ hop_num +=1
290
+ for child in curr_hop._children:
291
+ if hop_num not in result:
292
+ result[hop_num] = set()
293
+ result[hop_num].add(child)
294
+ self._walk_path(child, hop_num, result)
295
+
296
+
297
+ def print_path(self):
298
+ """
299
+ Prints out the path, hop by hop
300
+ """
301
+ for hop_num, hops in self.walk_path().items():
302
+ hop_list = list(hops)
303
+ hop_list.sort()
304
+ print(f"***** hop {hop_num} *****")
305
+ for hop in hop_list:
306
+ print(f"\t{hop}")
307
+
308
+
309
+ def get_path(self) -> list[dict]:
310
+ """
311
+ Returns hops as a list of dicts - for very basic displaying in
312
+ a table. In the future want to beef up the cli script
313
+ so that it can do nested tables, which is really
314
+ what we need for this.
315
+ """
316
+ result = []
317
+ for hop_num, hops in self.walk_path().items():
318
+ hop_list = list(hops)
319
+ hop_list.sort()
320
+ hop_str = "\n".join([str(h) for h in hop_list]) + "\n"
321
+ result.append({'hop_num':hop_num, 'hops': hop_str})
322
+
323
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: umnetdb-utils
3
- Version: 0.1.4
3
+ Version: 0.2.0
4
4
  Summary: Helper classes for querying UMnet databases
5
5
  License: MIT
6
6
  Author: Amy Liebowitz
@@ -16,6 +16,7 @@ Requires-Dist: oracledb (>=3.1.0,<4.0.0)
16
16
  Requires-Dist: psycopg[binary] (>=3.2.9,<4.0.0)
17
17
  Requires-Dist: python-decouple (>=3.8,<4.0)
18
18
  Requires-Dist: sqlalchemy (>=2.0.41,<3.0.0)
19
+ Requires-Dist: typer (>=0.16.0,<0.17.0)
19
20
  Description-Content-Type: text/markdown
20
21
 
21
22
  # umnetdb-utils
@@ -0,0 +1,12 @@
1
+ umnetdb_utils/__init__.py,sha256=YrT7drJ2MlU287z4--8EQI3fKLQZBlVxaCacU1yoDtg,132
2
+ umnetdb_utils/base.py,sha256=LWVhrbShhuYekXZxwvhhVUkH6DHd_wnJ2QZ_zw3fKqM,6139
3
+ umnetdb_utils/cli.py,sha256=tq_XxGrCWJacP3tzxTULPHeuvbh1AU_GHXHY1jkI4-U,3349
4
+ umnetdb_utils/umnetdb.py,sha256=bzMwItOfCgpKhCGwmghqAyp9gKvckGsUIGvSJC1iUho,17122
5
+ umnetdb_utils/umnetdisco.py,sha256=D9MiF_QrYznYL_ozLUmD1ASzkGDs1jqZ_XksNV7wW3o,8018
6
+ umnetdb_utils/umnetequip.py,sha256=Nnh_YCcqGpYRp2KsS8qbAGJ9AgTeYB4umeYeP9_B-6A,5440
7
+ umnetdb_utils/umnetinfo.py,sha256=vqFvLbIu7Wu650RTqzaELpbKhq6y91fgBXM5Q_elRl4,18443
8
+ umnetdb_utils/utils.py,sha256=kAVLjX4UvN5VdllYPN2t-q386iDjsZZ-Bp8G_ZTchbI,9418
9
+ umnetdb_utils-0.2.0.dist-info/METADATA,sha256=kkv7rHDme5K2VXBfe24KFg6qBOsezgLZc8XYku5dpwU,1595
10
+ umnetdb_utils-0.2.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
11
+ umnetdb_utils-0.2.0.dist-info/entry_points.txt,sha256=nNmxVRlYLaeepjDKNCyARBF3d8igHtZGTVA_zoglevI,50
12
+ umnetdb_utils-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ umnetdb=umnetdb_utils.cli:main
3
+
@@ -1,10 +0,0 @@
1
- umnetdb_utils/__init__.py,sha256=QJaytbr4ccKESiwaKjpf1b4b8s2cHNfCDdnCOs1tmoI,131
2
- umnetdb_utils/base.py,sha256=aC5oKSB5Ox9QtylmHV2uvEUVUx4CRLZTgH6HpQJ6DZo,6064
3
- umnetdb_utils/umnetdb.py,sha256=IxqpAcPZn71HVV_oL3ryvbKS7cupUsyM6rJSGk6NLaU,4554
4
- umnetdb_utils/umnetdisco.py,sha256=Z2XwT79jKO_avd3w_z99DDEdAikrfKxYm1JYHRWqvG4,7841
5
- umnetdb_utils/umnetequip.py,sha256=jOW5kvk0FXtdHv8PA4rYT_PWfLdMiq83Mjwmy-c1DN8,5701
6
- umnetdb_utils/umnetinfo.py,sha256=MH1YDW4OWtHD46qfYb5Pv40vPSbL0GrMNW5gAhdlihE,18445
7
- umnetdb_utils/utils.py,sha256=wU6QMYfofj7trX3QeqXty0btbGdhP_RUaSqA7QTflFM,991
8
- umnetdb_utils-0.1.4.dist-info/METADATA,sha256=JI2hwHkX3B8y-GLwS0yywAiFszcqUlkiWUyga0A2KaY,1555
9
- umnetdb_utils-0.1.4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
10
- umnetdb_utils-0.1.4.dist-info/RECORD,,