fwshift 0.31.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.
Files changed (87) hide show
  1. fwshift/__init__.py +7 -0
  2. fwshift/__main__.py +12 -0
  3. fwshift/appmap.py +378 -0
  4. fwshift/audit.py +1807 -0
  5. fwshift/cli.py +1199 -0
  6. fwshift/compare.py +921 -0
  7. fwshift/conduits.py +655 -0
  8. fwshift/emitters/__init__.py +5 -0
  9. fwshift/emitters/asa.py +1188 -0
  10. fwshift/emitters/aws.py +738 -0
  11. fwshift/emitters/azure.py +606 -0
  12. fwshift/emitters/barracuda.py +513 -0
  13. fwshift/emitters/checkpoint.py +929 -0
  14. fwshift/emitters/cisco.py +1532 -0
  15. fwshift/emitters/fmc_loader.py +734 -0
  16. fwshift/emitters/forcepoint.py +815 -0
  17. fwshift/emitters/fortios.py +806 -0
  18. fwshift/emitters/gcp.py +396 -0
  19. fwshift/emitters/huawei.py +510 -0
  20. fwshift/emitters/ios.py +566 -0
  21. fwshift/emitters/iptables.py +639 -0
  22. fwshift/emitters/junos.py +748 -0
  23. fwshift/emitters/meraki.py +683 -0
  24. fwshift/emitters/nftables.py +490 -0
  25. fwshift/emitters/nsx.py +492 -0
  26. fwshift/emitters/opnsense.py +518 -0
  27. fwshift/emitters/panos.py +546 -0
  28. fwshift/emitters/pfsense.py +1367 -0
  29. fwshift/emitters/routeros.py +575 -0
  30. fwshift/emitters/smc_loader.py +730 -0
  31. fwshift/emitters/sonicwall.py +904 -0
  32. fwshift/emitters/sophos.py +1158 -0
  33. fwshift/emitters/stormshield.py +553 -0
  34. fwshift/emitters/terraform.py +343 -0
  35. fwshift/emitters/watchguard.py +511 -0
  36. fwshift/errors.py +15 -0
  37. fwshift/hits.py +247 -0
  38. fwshift/logreaders/__init__.py +73 -0
  39. fwshift/logreaders/checkpoint.py +388 -0
  40. fwshift/logreaders/fortios.py +177 -0
  41. fwshift/logreaders/iptables.py +186 -0
  42. fwshift/logreaders/panos.py +202 -0
  43. fwshift/matrix.py +950 -0
  44. fwshift/model.py +319 -0
  45. fwshift/names.py +108 -0
  46. fwshift/narrow.py +284 -0
  47. fwshift/netutil.py +155 -0
  48. fwshift/options.py +54 -0
  49. fwshift/parsers/__init__.py +5 -0
  50. fwshift/parsers/asa.py +1864 -0
  51. fwshift/parsers/aws.py +1042 -0
  52. fwshift/parsers/azure.py +1187 -0
  53. fwshift/parsers/barracuda.py +809 -0
  54. fwshift/parsers/checkpoint.py +1309 -0
  55. fwshift/parsers/cisco.py +1994 -0
  56. fwshift/parsers/forcepoint.py +1562 -0
  57. fwshift/parsers/fortios.py +1227 -0
  58. fwshift/parsers/gcp.py +918 -0
  59. fwshift/parsers/huawei.py +934 -0
  60. fwshift/parsers/ios.py +1660 -0
  61. fwshift/parsers/iptables.py +875 -0
  62. fwshift/parsers/junos.py +1162 -0
  63. fwshift/parsers/meraki.py +751 -0
  64. fwshift/parsers/nftables.py +2555 -0
  65. fwshift/parsers/nsx.py +893 -0
  66. fwshift/parsers/opnsense.py +1158 -0
  67. fwshift/parsers/panos.py +406 -0
  68. fwshift/parsers/pfsense.py +1677 -0
  69. fwshift/parsers/routeros.py +1549 -0
  70. fwshift/parsers/smc_dump.py +298 -0
  71. fwshift/parsers/sonicwall.py +814 -0
  72. fwshift/parsers/sophos.py +1034 -0
  73. fwshift/parsers/stormshield.py +884 -0
  74. fwshift/parsers/terraform.py +1371 -0
  75. fwshift/parsers/watchguard.py +527 -0
  76. fwshift/regions.py +167 -0
  77. fwshift/report.py +221 -0
  78. fwshift/simulate.py +152 -0
  79. fwshift/transform.py +1735 -0
  80. fwshift/usage.py +1132 -0
  81. fwshift/xmlread.py +43 -0
  82. fwshift-0.31.0.dist-info/METADATA +855 -0
  83. fwshift-0.31.0.dist-info/RECORD +87 -0
  84. fwshift-0.31.0.dist-info/WHEEL +5 -0
  85. fwshift-0.31.0.dist-info/entry_points.txt +2 -0
  86. fwshift-0.31.0.dist-info/licenses/LICENSE +674 -0
  87. fwshift-0.31.0.dist-info/top_level.txt +1 -0
fwshift/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ # FWShift - multi-vendor firewall policy converter
2
+ # Copyright (C) 2026 Jurgen Kobierczynski
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ """FWShift - multi-vendor firewall policy converter."""
6
+
7
+ __version__ = "0.31.0"
fwshift/__main__.py ADDED
@@ -0,0 +1,12 @@
1
+ # FWShift - multi-vendor firewall policy converter
2
+ # Copyright (C) 2026 Jurgen Kobierczynski
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ """Allow ``python -m fwshift``."""
6
+
7
+ import sys
8
+
9
+ from .cli import main
10
+
11
+ if __name__ == "__main__":
12
+ sys.exit(main())
fwshift/appmap.py ADDED
@@ -0,0 +1,378 @@
1
+ # FWShift - multi-vendor firewall policy converter
2
+ # Copyright (C) 2026 Jurgen Kobierczynski
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ """Application <-> layer-4 mapping tables.
6
+
7
+ ``APP_PORTS`` lists the well-known default ports of common PAN-OS App-IDs. It
8
+ is **not** Palo Alto's application database: it holds the standard port of
9
+ each protocol, which is what a layer-4 policy can express. Extend or override
10
+ it per conversion with ``--app-map``.
11
+
12
+ A port spec is ``tcp/80``, ``udp/1812-1813``, ``icmp/8``, ``icmp/any``,
13
+ ``icmp6/128`` or ``ip/47``.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import re
20
+ from typing import Dict, List, Optional, Tuple
21
+
22
+ from .errors import FwshiftError
23
+ from .model import Protocol, ServiceEntry
24
+
25
+ APP_PORTS: Dict[str, List[str]] = {
26
+ # web and name services
27
+ "web-browsing": ["tcp/80"],
28
+ "ssl": ["tcp/443"],
29
+ "dns": ["tcp/53", "udp/53"],
30
+ "ntp": ["udp/123"],
31
+ "dhcp": ["udp/67-68"],
32
+ # remote access and file transfer
33
+ "ssh": ["tcp/22"],
34
+ "telnet": ["tcp/23"],
35
+ "ms-rdp": ["tcp/3389"],
36
+ "ftp": ["tcp/21"],
37
+ "tftp": ["udp/69"],
38
+ "ms-ds-smb": ["tcp/445"],
39
+ # mail
40
+ "smtp": ["tcp/25"],
41
+ "imap": ["tcp/143"],
42
+ "pop3": ["tcp/110"],
43
+ # directory, authentication, management
44
+ "ldap": ["tcp/389", "udp/389"],
45
+ "kerberos": ["tcp/88", "udp/88"],
46
+ "radius": ["udp/1812-1813"],
47
+ "tacacs-plus": ["tcp/49"],
48
+ "snmp": ["udp/161"],
49
+ "snmp-trap": ["udp/162"],
50
+ "syslog": ["udp/514"],
51
+ # databases
52
+ "mysql": ["tcp/3306"],
53
+ "mssql-db": ["tcp/1433"],
54
+ "postgres": ["tcp/5432"],
55
+ "oracle": ["tcp/1521"],
56
+ # routing and tunnelling
57
+ "bgp": ["tcp/179"],
58
+ "ospf": ["ip/89"],
59
+ "ike": ["udp/500"],
60
+ "ipsec-esp-udp": ["udp/4500"],
61
+ "ipsec-esp": ["ip/50"],
62
+ "ipsec-ah": ["ip/51"],
63
+ "gre": ["ip/47"],
64
+ # ICMP
65
+ "ping": ["icmp/8"],
66
+ "icmp": ["icmp/any"],
67
+ "ping6": ["icmp6/128"],
68
+ "ipv6-icmp": ["icmp6/any"],
69
+ # industrial protocols
70
+ "modbus": ["tcp/502"],
71
+ "dnp3": ["tcp/20000", "udp/20000"],
72
+ "siemens-s7": ["tcp/102"],
73
+ "bacnet": ["udp/47808"],
74
+ "mqtt": ["tcp/1883"],
75
+ }
76
+
77
+ # IP protocol number -> App-ID, for platforms that match these as applications.
78
+ IP_PROTOCOL_APPS: Dict[int, str] = {
79
+ 2: "igmp",
80
+ 47: "gre",
81
+ 50: "ipsec-esp",
82
+ 51: "ipsec-ah",
83
+ 88: "eigrp",
84
+ 89: "ospf",
85
+ 103: "pim",
86
+ 112: "vrrp",
87
+ }
88
+
89
+
90
+ # FortiOS factory-default services: assumed when a FortiOS input refers to one
91
+ # without defining it (a fragment rather than a full backup), and reserved names
92
+ # when writing FortiOS.
93
+ FORTIOS_DEFAULT_SERVICES: Dict[str, List[str]] = {
94
+ "ALL_TCP": ["tcp/1-65535"],
95
+ "ALL_UDP": ["udp/1-65535"],
96
+ "ALL_ICMP": ["icmp/any"],
97
+ "ALL_ICMP6": ["icmp6/any"],
98
+ "PING": ["icmp/8"],
99
+ "HTTP": ["tcp/80"],
100
+ "HTTPS": ["tcp/443"],
101
+ "DNS": ["tcp/53", "udp/53"],
102
+ "SSH": ["tcp/22"],
103
+ "TELNET": ["tcp/23"],
104
+ "FTP": ["tcp/21"],
105
+ "TFTP": ["udp/69"],
106
+ "SMTP": ["tcp/25"],
107
+ "SMTPS": ["tcp/465"],
108
+ "IMAP": ["tcp/143"],
109
+ "IMAPS": ["tcp/993"],
110
+ "POP3": ["tcp/110"],
111
+ "POP3S": ["tcp/995"],
112
+ "NTP": ["tcp/123", "udp/123"],
113
+ "SNMP": ["tcp/161-162", "udp/161-162"],
114
+ "SYSLOG": ["udp/514"],
115
+ "RDP": ["tcp/3389"],
116
+ "SMB": ["tcp/445"],
117
+ "LDAP": ["tcp/389"],
118
+ "LDAPS": ["tcp/636"],
119
+ "KERBEROS": ["tcp/88", "udp/88"],
120
+ "RADIUS": ["udp/1812-1813"],
121
+ "MYSQL": ["tcp/3306"],
122
+ "MS-SQL": ["tcp/1433-1434"],
123
+ "BGP": ["tcp/179"],
124
+ "DHCP": ["udp/67-68"],
125
+ "IKE": ["udp/500", "udp/4500"],
126
+ "ESP": ["ip/50"],
127
+ "AH": ["ip/51"],
128
+ "GRE": ["ip/47"],
129
+ "OSPF": ["ip/89"],
130
+ }
131
+
132
+
133
+ # Names of the services a factory-default FortiGate defines. Objects from another
134
+ # platform that carry one of these names are renamed when FortiOS is written.
135
+ FORTIOS_FACTORY_SERVICE_NAMES = frozenset(
136
+ """ALL ALL_TCP ALL_UDP ALL_ICMP ALL_ICMP6 AFS3 AH AOL BGP CVSPSERVER DCE-RPC DHCP DHCP6 DNS ESP FINGER FTP
137
+ FTP_GET FTP_PUT GOPHER GRE GTP H323 HTTP HTTPS IKE IMAP IMAPS INFO_ADDRESS INFO_REQUEST IRC
138
+ Internet-Locator-Service KERBEROS L2TP LDAP LDAP_UDP LDAPS MGCP MMS MS-SQL MYSQL NFS NNTP NONE NTP NetMeeting
139
+ ONC-RPC OSPF PC-Anywhere PING PING6 POP3 POP3S PPTP QUAKE RADIUS RADIUS-OLD RAUDIO RDP REXEC RIP RLOGIN RSH
140
+ RTSP SAMBA SCCP SIP SIP-MSNmessenger SMB SMTP SMTPS SNMP SOCKS SQUID SSH SYSLOG TALK TELNET TFTP TIMESTAMP
141
+ TRACEROUTE UUCP VDOLIVE VNC WAIS WINFRAME WINS X-WINDOWS webproxy""".split()
142
+ ) | frozenset(FORTIOS_DEFAULT_SERVICES) | frozenset({"Email Access", "Web Access", "Windows AD", "Exchange Server"})
143
+
144
+ _PORTS = re.compile(r"[0-9]{1,5}(-[0-9]{1,5})?(,[0-9]{1,5}(-[0-9]{1,5})?)*")
145
+
146
+
147
+ def valid_ports(text: str) -> bool:
148
+ """True for a port list such as ``80,443,8000-8080`` with every port in 0-65535 and ordered ranges."""
149
+ if not _PORTS.fullmatch(text):
150
+ return False
151
+ for part in text.split(","):
152
+ first, _, last = part.partition("-")
153
+ if int(first) > 65535 or int(last or first) > 65535 or int(last or first) < int(first):
154
+ return False
155
+ return True
156
+
157
+
158
+ def parse_spec(spec: str) -> ServiceEntry:
159
+ proto, sep, value = spec.strip().lower().partition("/")
160
+ if not sep or not value:
161
+ raise FwshiftError(f"invalid port spec '{spec}' (expected e.g. tcp/443)")
162
+ if proto in ("tcp", "udp", "sctp"):
163
+ if not valid_ports(value):
164
+ raise FwshiftError(f"invalid port spec '{spec}': expected ports such as 443 or 8000-8080")
165
+ return ServiceEntry(Protocol(proto), dst_ports=value)
166
+ if proto not in ("icmp", "icmp6", "ip"):
167
+ raise FwshiftError(f"invalid port spec '{spec}': unknown protocol '{proto}'")
168
+ if value == "any":
169
+ if proto == "ip":
170
+ raise FwshiftError(f"invalid port spec '{spec}': that would match every protocol")
171
+ number = None
172
+ elif value.isascii() and value.isdigit() and int(value) <= 255 and (proto != "ip" or int(value) > 0):
173
+ number = int(value)
174
+ else:
175
+ raise FwshiftError(f"invalid port spec '{spec}': expected a number up to 255" + ("" if proto == "ip" else " or 'any'"))
176
+ if proto == "ip":
177
+ return ServiceEntry(Protocol.IP, ip_protocol=number)
178
+ return ServiceEntry(Protocol.ICMP if proto == "icmp" else Protocol.ICMP6, icmp_type=number)
179
+
180
+
181
+ def entries_for(app: str, overrides: Dict[str, List[str]]) -> Optional[List[ServiceEntry]]:
182
+ """Layer-4 definition of an application, or None when it is not in the table."""
183
+ specs = overrides.get(app, APP_PORTS.get(app))
184
+ if specs is None:
185
+ return None
186
+ return [parse_spec(spec) for spec in specs]
187
+
188
+
189
+ def load_app_map(path: str) -> Dict[str, List[str]]:
190
+ try:
191
+ with open(path, encoding="utf-8") as handle:
192
+ data = json.load(handle)
193
+ except (OSError, ValueError) as exc:
194
+ raise FwshiftError(f"cannot read application map {path}: {exc}") from None
195
+ if not isinstance(data, dict):
196
+ raise FwshiftError(f"{path}: expected a JSON object of application -> list of port specs")
197
+ out: Dict[str, List[str]] = {}
198
+ for app, specs in data.items():
199
+ if isinstance(specs, str):
200
+ specs = [specs]
201
+ if not isinstance(specs, list) or not all(isinstance(s, str) for s in specs):
202
+ raise FwshiftError(f"{path}: '{app}' must map to a list of port specs")
203
+ for spec in specs:
204
+ parse_spec(spec)
205
+ out[str(app)] = specs
206
+ return out
207
+
208
+
209
+ # -- Sophos Firewall --------------------------------------------------------------------------------
210
+
211
+ # IP protocols as Sophos names them in a service of type IP: the keywords of the IANA registry, and
212
+ # IPProto<n> for numbers without one. The API documentation shows the start of the list (HOPOPT, ICMP,
213
+ # IGMP, GGP, IP, ST, TCP, CBT, EGP, IGP ... UDP, GRE, ESP, AH, SCTP, UDPLite, IPProto143 ... IPProto255);
214
+ # the names in between are taken from the registry.
215
+ SOPHOS_PROTOCOL_NAMES: Dict[int, str] = {
216
+ 0: "HOPOPT", 1: "ICMP", 2: "IGMP", 3: "GGP", 4: "IP", 5: "ST", 6: "TCP", 7: "CBT", 8: "EGP", 9: "IGP",
217
+ 10: "BBN-RCC-MON", 11: "NVP-II", 12: "PUP", 13: "ARGUS", 14: "EMCON", 15: "XNET", 16: "CHAOS", 17: "UDP",
218
+ 18: "MUX", 19: "DCN-MEAS", 20: "HMP", 21: "PRM", 22: "XNS-IDP", 23: "TRUNK-1", 24: "TRUNK-2", 25: "LEAF-1",
219
+ 26: "LEAF-2", 27: "RDP", 28: "IRTP", 29: "ISO-TP4", 30: "NETBLT", 31: "MFE-NSP", 32: "MERIT-INP", 33: "DCCP",
220
+ 34: "3PC", 35: "IDPR", 36: "XTP", 37: "DDP", 38: "IDPR-CMTP", 39: "TP++", 40: "IL", 41: "IPv6", 42: "SDRP",
221
+ 43: "IPv6-Route", 44: "IPv6-Frag", 45: "IDRP", 46: "RSVP", 47: "GRE", 48: "DSR", 49: "BNA", 50: "ESP", 51: "AH",
222
+ 52: "I-NLSP", 53: "SWIPE", 54: "NARP", 55: "MOBILE", 56: "TLSP", 57: "SKIP", 58: "IPv6-ICMP", 59: "IPv6-NoNxt",
223
+ 60: "IPv6-Opts", 62: "CFTP", 64: "SAT-EXPAK", 65: "KRYPTOLAN", 66: "RVD", 67: "IPPC", 69: "SAT-MON", 70: "VISA",
224
+ 71: "IPCV", 72: "CPNX", 73: "CPHB", 74: "WSN", 75: "PVP", 76: "BR-SAT-MON", 77: "SUN-ND", 78: "WB-MON",
225
+ 79: "WB-EXPAK", 80: "ISO-IP", 81: "VMTP", 82: "SECURE-VMTP", 83: "VINES", 84: "TTP", 85: "NSFNET-IGP", 86: "DGP",
226
+ 87: "TCF", 88: "EIGRP", 89: "OSPFIGP", 90: "Sprite-RPC", 91: "LARP", 92: "MTP", 93: "AX.25", 94: "IPIP",
227
+ 95: "MICP", 96: "SCC-SP", 97: "ETHERIP", 98: "ENCAP", 100: "GMTP", 101: "IFMP", 102: "PNNI", 103: "PIM",
228
+ 104: "ARIS", 105: "SCPS", 106: "QNX", 107: "A/N", 108: "IPComp", 109: "SNP", 110: "Compaq-Peer",
229
+ 111: "IPX-in-IP", 112: "VRRP", 113: "PGM", 115: "L2TP", 116: "DDX", 117: "IATP", 118: "STP", 119: "SRP",
230
+ 120: "UTI", 121: "SMP", 122: "SM", 123: "PTP", 124: "ISIS", 125: "FIRE", 126: "CRTP", 127: "CRUDP",
231
+ 128: "SSCOPMCE", 129: "IPLT", 130: "SPS", 131: "PIPE", 132: "SCTP", 133: "FC", 134: "RSVP-E2E-IGNORE",
232
+ 135: "Mobility Header", 136: "UDPLite", 137: "MPLS-in-IP", 138: "manet", 139: "HIP", 140: "Shim6", 141: "WESP",
233
+ 142: "ROHC",
234
+ } # fmt: skip
235
+ # The names the API documentation itself shows: an emitter can write these without a second look.
236
+ SOPHOS_PROTOCOLS_CONFIRMED = frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 17, 47, 50, 51, 132, 136})
237
+ _SOPHOS_PROTOCOL_ALIASES = {"ospf": 89, "ipv6-icmp": 58, "icmpv6": 58, "icmp6": 58, "ipv4": 4, "ip-in-ip": 4, "isis over ipv4": 124,
238
+ "ttp/iptm": 84, "iptm": 84, "udp-lite": 136} # fmt: skip
239
+ _SOPHOS_PROTOCOL_NUMBERS = {name.lower(): number for number, name in SOPHOS_PROTOCOL_NAMES.items()}
240
+
241
+
242
+ def sophos_protocol_number(name: str) -> Optional[int]:
243
+ """The IP protocol behind a name of a Sophos service of type IP: a keyword, ``IPProto<n>`` or a number."""
244
+ text = name.strip().lower()
245
+ if text.startswith("ipproto"):
246
+ text = text[len("ipproto"):]
247
+ if text.isascii() and text.isdigit() and len(text) <= 3:
248
+ return int(text) if int(text) <= 255 else None
249
+ if text in _SOPHOS_PROTOCOL_NUMBERS:
250
+ return _SOPHOS_PROTOCOL_NUMBERS[text]
251
+ return _SOPHOS_PROTOCOL_ALIASES.get(text)
252
+
253
+
254
+ # ICMP types and codes are numbers in the XML API, -1 for any; these are the values it lists.
255
+ SOPHOS_ICMP_TYPES = frozenset([0, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18] + list(range(30, 41)))
256
+ SOPHOS_ICMP6_TYPES = frozenset([0, 1, 2, 3, 4, 100, 101] + list(range(128, 159)) + [200, 201])
257
+ SOPHOS_ICMP_CODES = frozenset(range(0, 16))
258
+ # Names, for a file that spells them out (the web console does).
259
+ SOPHOS_ICMP_NAMES = {
260
+ "echo reply": 0, "destination unreachable": 3, "source quench": 4, "redirect": 5, "alternate host address": 6,
261
+ "echo": 8, "echo request": 8, "router advertisement": 9, "router selection": 10, "router solicitation": 10,
262
+ "time exceeded": 11, "parameter problem": 12, "timestamp": 13, "timestamp reply": 14, "information request": 15,
263
+ "information reply": 16, "address mask request": 17, "address mask reply": 18, "traceroute": 30,
264
+ } # fmt: skip
265
+ SOPHOS_ICMP6_NAMES = {
266
+ "destination unreachable": 1, "packet too big": 2, "time exceeded": 3, "parameter problem": 4, "echo request": 128,
267
+ "echo reply": 129, "multicast listener query": 130, "multicast listener report": 131, "multicast listener done": 132,
268
+ "router solicitation": 133, "router advertisement": 134, "neighbor solicitation": 135, "neighbor advertisement": 136,
269
+ "redirect": 137, "redirect message": 137,
270
+ } # fmt: skip
271
+
272
+ # Services a Sophos Firewall comes with. An object of another platform that carries one of these names is
273
+ # renamed: an import overwrites what has the same name.
274
+ SOPHOS_FACTORY_SERVICE_NAMES = frozenset(
275
+ """AH AOL BGP DHCP DNS ESP FINGER FTP FTP_GET FTP_PUT GOPHER GRE H323 HTTP HTTPS ICMP_ANY IKE IMAP IMAPS
276
+ Internet-Locator-Service IRC L2TP LDAP NetMeeting NFS NNTP NTP OSPF PC-Anywhere PING PING6 POP3 POP3S PPTP QUAKE
277
+ RAUDIO REXEC RIP RLOGIN SAMBA SIP SIP-MSNmessenger SMTP SMTPS SMTP(S) SNMP SSH SYSLOG TALK TCP TELNET TFTP MGCP
278
+ UUCP VDOLIVE WAIS WINFRAME X-WINDOWS UDP ICMP ICMPv6 RDP SMB TRACEROUTE VNC MS-SQL MYSQL RADIUS KERBEROS
279
+ Chromecast SSLVPN Any""".split()
280
+ )
281
+
282
+
283
+ # -- Cisco Secure Firewall -------------------------------------------------------------------------
284
+
285
+ _ALNUM = re.compile(r"[^a-z0-9]")
286
+ # Other spellings of a protocol than the keyword of the IANA registry, as the managers and their
287
+ # tools write them. A name that means two things somewhere is left out.
288
+ _PROTOCOL_ALIASES = {"icmpv6": 58, "icmp6": 58, "ipv6icmp": 58, "ospf": 89, "ipinip": 4, "ipv4": 4, "ipencap": 4,
289
+ "udplite": 136, "tpplusplus": 39, "threepc": 34, "nvp2": 11, "st2": 5, "ipv6nonxt": 59,
290
+ "ipv6route": 43, "ipv6frag": 44, "ipv6opts": 60, "mobilityheader": 135} # fmt: skip
291
+ _IANA_NUMBERS = {_ALNUM.sub("", name.lower()): number for number, name in SOPHOS_PROTOCOL_NAMES.items()}
292
+
293
+
294
+ def protocol_number(name: str) -> Optional[int]:
295
+ """The IP protocol behind a name or a number as a Cisco manager writes it; None when that is not known.
296
+
297
+ Names are compared without case and punctuation, against the keywords of the IANA registry.
298
+ """
299
+ text = name.strip()
300
+ if is_plain_number(text):
301
+ # 0 is "ip" on a Cisco command line, which is every protocol: not a protocol to convert as one.
302
+ return int(text) if len(text) <= 3 and 0 < int(text) <= 255 else None
303
+ key = _ALNUM.sub("", text.lower())
304
+ if key in ("ip", "all", "any", "hopopt"):
305
+ return None
306
+ if key in _PROTOCOL_ALIASES:
307
+ return _PROTOCOL_ALIASES[key]
308
+ return _IANA_NUMBERS.get(key)
309
+
310
+
311
+ def is_plain_number(text: str) -> bool:
312
+ return text.isascii() and text.isdigit()
313
+
314
+
315
+ # The Device Manager names protocols, ICMP types and ICMP codes with an enumeration. The names are the
316
+ # keywords of the IANA registries without punctuation; the numbers were taken from the registries.
317
+ FDM_PROTOCOLS: Dict[str, int] = {
318
+ "IGMP": 2, "GGP": 3, "ST2": 5, "TCP": 6, "CBT": 7, "EGP": 8, "IGP": 9, "BBNRCCMON": 10, "NVP2": 11, "PUP": 12,
319
+ "ARGUS": 13, "EMCON": 14, "XNET": 15, "CHAOS": 16, "UDP": 17, "MUX": 18, "DCNMEAS": 19, "HMP": 20, "PRM": 21,
320
+ "XNSIDP": 22, "TRUNK1": 23, "TRUNK2": 24, "LEAF1": 25, "LEAF2": 26, "RDP": 27, "IRTP": 28, "ISOTP4": 29,
321
+ "NETBLT": 30, "MFENSP": 31, "MERITINP": 32, "SEP": 33, "THREEPC": 34, "IDPR": 35, "XTP": 36, "DDP": 37,
322
+ "IDPRCMTP": 38, "TPPLUSPLUS": 39, "IL": 40, "SDRP": 42, "IDRP": 45, "RSVP": 46, "GRE": 47, "MHRP": 48, "BNA": 49,
323
+ "ESP": 50, "AH": 51, "INLSP": 52, "SWIPE": 53, "NARP": 54, "MOBILE": 55, "TLSP": 56, "SKIP": 57, "IPv6NONXT": 59,
324
+ "CFTP": 62, "SATEXPAK": 64, "KRYPTOLAN": 65, "RVD": 66, "IPPC": 67, "SATMON": 69, "VISA": 70, "IPCV": 71,
325
+ "CPNX": 72, "CPHB": 73, "WSN": 74, "PVP": 75, "BRSATMON": 76, "SUNND": 77, "WBMON": 78, "WBEXPAK": 79,
326
+ "ISOIP": 80, "VMTP": 81, "SECUREVMTP": 82, "VINES": 83, "TTP": 84, "NSFNETIGP": 85, "DGP": 86, "TCF": 87,
327
+ "EIGRP": 88, "OSPFIGP": 89, "SPRITERPC": 90, "LARP": 91, "MTP": 92, "AX25": 93, "IPIP": 94, "MICP": 95,
328
+ "SCCSP": 96, "ETHERIP": 97, "ENCAP": 98, "GMTP": 100, "IFMP": 101, "PNNI": 102, "PIM": 103, "ARIS": 104,
329
+ "SCPS": 105, "QNX": 106, "AN": 107, "IPCOMP": 108, "SNP": 109, "COMPAQPEER": 110, "IPXINIP": 111, "VRRP": 112,
330
+ "PGM": 113, "L2TP": 115, "DDX": 116, "IATP": 117, "ST": 118, "SRP": 119, "UTI": 120, "SMP": 121, "SM": 122,
331
+ "PTP": 123, "ISIS": 124, "FIRE": 125, "CRTP": 126, "CRUDP": 127, "SSCOPMCE": 128, "IPLT": 129, "SPS": 130,
332
+ "PIPE": 131, "SCTP": 132, "FC": 133,
333
+ } # fmt: skip
334
+ FDM_PROTOCOL_NAMES: Dict[int, str] = {number: name for name, number in FDM_PROTOCOLS.items()}
335
+ FDM_ICMP_TYPES: Dict[str, int] = {
336
+ "ECHO_REPLY": 0, "DESTINATION_UNREACHABLE": 3, "SOURCE_QUENCH": 4, "REDIRECT_MESSAGE": 5, "ALTERNATE_HOST_ADDRESS": 6,
337
+ "ECHO_REQUEST": 8, "ROUTER_ADVERTISEMENT": 9, "ROUTER_SOLICITATION": 10, "TIME_EXCEEDED": 11, "PARAMETER_PROBLEM": 12,
338
+ "TIMESTAMP": 13, "TIMESTAMP_REPLY": 14, "INFO_REQUEST": 15, "INFO_REPLY": 16, "ADDR_MASK_REQUEST": 17,
339
+ "ADDR_MASK_REPLY": 18, "TRACEROUTE": 30, "DATAGRAM_CONVERSION_ERROR": 31, "MOBILE_HOST_REDIRECT": 32,
340
+ "WHERE_ARE_YOU": 33, "HERE_I_AM": 34, "MOBILE_REG_REQUEST": 35, "MOBILE_REG_REPLY": 36, "DOMAIN_NAME_REQUEST": 37,
341
+ "DOMAIN_NAME_REPLY": 38, "SKIP_ALGORITHM_DISCOVERY_PROTOCOL": 39, "PHOTURIS": 40, "EXPERIMENTAL_MOB_PROTOCOLS": 41,
342
+ } # fmt: skip
343
+ # Code name -> (type it belongs to, code).
344
+ FDM_ICMP_CODES: Dict[str, Tuple[int, int]] = {
345
+ "NET_UNREACHABLE": (3, 0), "HOST_UNREACHABLE": (3, 1), "PROTOCOL_UNREACHABLE": (3, 2), "PORT_UNREACHABLE": (3, 3),
346
+ "FRAGMENTATION_NEEDED": (3, 4), "SOURCE_ROUTE_FAILED": (3, 5), "DEST_NETWORK_UNKNOWN": (3, 6),
347
+ "DEST_HOST_UNKNOWN": (3, 7), "SRC_HOST_ISOLATED": (3, 8), "COMMUNICATION_DEST_NET_PROHIBITED": (3, 9),
348
+ "COMMUNICATION_DEST_HOST_PROHIBITED": (3, 10), "DEST_NET_UNREACHABLE_FOR_TOS": (3, 11),
349
+ "DEST_HOST_UNREACHABLE_FOR_TOS": (3, 12), "COMM_ADMINISTRATIVELY_PROHIBITED": (3, 13),
350
+ "HOST_PRECEDENCE_VIOLATION": (3, 14), "PRECEDENCE_CUTOFF": (3, 15), "REDIRECT_DATAGRAM_NETWORK": (5, 0),
351
+ "REDIRECT_DATAGRAM_HOST": (5, 1), "REDIRECT_DATAGRAM_SERVICE_NETWORK": (5, 2), "REDIRECT_DATAGRAM_SERVICE_HOST": (5, 3),
352
+ "ALTERNATE_HOST_ADDR": (6, 0), "NORMAL_ROUTER_ADV": (9, 0), "DO_NOT_ROUTE_COMMON_TRAFFIC": (9, 16),
353
+ "TTL_EXPIRED_TRANSIT": (11, 0), "FRAG_ASSEMBLY": (11, 1), "PTR_ERROR": (12, 0), "MISSING_REQD_OPTION": (12, 1),
354
+ "BAD_LENGTH": (12, 2), "BAD_SPI": (40, 0), "AUTH_FAILED": (40, 1), "DECOMPRESSION_FAILED": (40, 2),
355
+ "DECRYPTION_FAILED": (40, 3), "NEED_AUTHENTICATION": (40, 4), "NEED_AUTHORIZATION": (40, 5),
356
+ } # fmt: skip
357
+ FDM_ICMP6_TYPES: Dict[str, int] = {
358
+ "DESTINATION_UNREACHABLE": 1, "PACKET_TOO_BIG": 2, "TIME_EXCEEDED": 3, "PARAMETER_PROBLEM": 4, "ECHO_REQUEST": 128,
359
+ "ECHO_REPLY": 129, "MULTICAST_LISTENER_QUERY": 130, "MULTICAST_LISTENER_REPORT": 131, "MULTICAST_LISTENER_DONE": 132,
360
+ "ROUTER_SOLICITATION": 133, "ROUTER_ADVERTISEMENT": 134, "NEIGHBOUR_SOLICITATION": 135,
361
+ "NEIGHBOUR_ADVERTISEMENT": 136, "REDIRECT_MESSAGE": 137, "ROUTER_RENUMBERING": 138, "ICMP_NODE_INFO_QUERY": 139,
362
+ "ICMP_NODE_INFO_RESPONSE": 140, "INVERSE_NEIGHBOR_DISCOVERY_SOLICITATION": 141,
363
+ "INVERSE_NEIGHBOR_DISCOVERY_ADVERTISEMENT": 142, "VER2_MULTICAST_LISTENER_REPORT": 143,
364
+ "HOME_AGENT_ADDR_DISCOVERY_REQ": 144, "HOME_AGENT_ADDR_DISCOVERY_REPLY": 145, "MOBILE_PREFIX_SOLICITATION": 146,
365
+ "MOBILE_PREFIX_ADVERTISEMENT": 147, "CERT_PATH_SOLICITATION": 148, "CERT_PATH_ADVERTISEMENT": 149,
366
+ "ICMP_EXP_MOBILITY_PROTOCOLS": 150, "MULTICAST_ROUTER_ADVERTISEMENT": 151, "MULTICAST_ROUTER_SOLICITATION": 152,
367
+ "MULTICAST_ROUTER_TERMINATION": 153, "FMIPv6_MSG": 154, "RPL_CTRL_MSG": 155,
368
+ } # fmt: skip
369
+ FDM_ICMP6_CODES: Dict[str, Tuple[int, int]] = {
370
+ "NO_ROUTE_DEST": (1, 0), "COMMUNICATION_PROHIBITED": (1, 1), "BEYOND_SCOPE_SRC_ADDR": (1, 2),
371
+ "ADDRESS_UNREACHABLE": (1, 3), "PORT_UNREACHABLE": (1, 4), "SOURCE_ADDRESS_FAILED": (1, 5), "REJECT_ROUTE": (1, 6),
372
+ "ERROR_SRC_ROUTING_HEADER": (1, 7), "HOP_LIMIT_EXCEEDED": (3, 0), "FRAGMENT_REASSEMBLY_TIME_EXCEEDED": (3, 1),
373
+ "ERRONEOUS_HEADER_ENCOUNTERED": (4, 0), "UNRECOGNIZED_NEXT_HEADER_TYPE_ENCOUNTERED": (4, 1),
374
+ "UNRECOGNIZED_IPv6_OPTION_ENCOUNTERED": (4, 2), "ROUTER_RENUMBERING_COMMAND": (138, 0),
375
+ "ROUTER_RENUMBERING_RESULT": (138, 1), "SEQUENCE_NUMBER_RESET": (138, 255), "DATA_CONTAINS_IPv6": (139, 0),
376
+ "DATA_CONTAINS_NAME": (139, 1), "DATA_CONTAINS_IPv4": (139, 2), "SUCCESSFUL_REPLY": (140, 0),
377
+ "RESPONDER_REFUSES_ANSWER": (140, 1), "QTYPE_UNKNOWN": (140, 2),
378
+ } # fmt: skip