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.
- fwshift/__init__.py +7 -0
- fwshift/__main__.py +12 -0
- fwshift/appmap.py +378 -0
- fwshift/audit.py +1807 -0
- fwshift/cli.py +1199 -0
- fwshift/compare.py +921 -0
- fwshift/conduits.py +655 -0
- fwshift/emitters/__init__.py +5 -0
- fwshift/emitters/asa.py +1188 -0
- fwshift/emitters/aws.py +738 -0
- fwshift/emitters/azure.py +606 -0
- fwshift/emitters/barracuda.py +513 -0
- fwshift/emitters/checkpoint.py +929 -0
- fwshift/emitters/cisco.py +1532 -0
- fwshift/emitters/fmc_loader.py +734 -0
- fwshift/emitters/forcepoint.py +815 -0
- fwshift/emitters/fortios.py +806 -0
- fwshift/emitters/gcp.py +396 -0
- fwshift/emitters/huawei.py +510 -0
- fwshift/emitters/ios.py +566 -0
- fwshift/emitters/iptables.py +639 -0
- fwshift/emitters/junos.py +748 -0
- fwshift/emitters/meraki.py +683 -0
- fwshift/emitters/nftables.py +490 -0
- fwshift/emitters/nsx.py +492 -0
- fwshift/emitters/opnsense.py +518 -0
- fwshift/emitters/panos.py +546 -0
- fwshift/emitters/pfsense.py +1367 -0
- fwshift/emitters/routeros.py +575 -0
- fwshift/emitters/smc_loader.py +730 -0
- fwshift/emitters/sonicwall.py +904 -0
- fwshift/emitters/sophos.py +1158 -0
- fwshift/emitters/stormshield.py +553 -0
- fwshift/emitters/terraform.py +343 -0
- fwshift/emitters/watchguard.py +511 -0
- fwshift/errors.py +15 -0
- fwshift/hits.py +247 -0
- fwshift/logreaders/__init__.py +73 -0
- fwshift/logreaders/checkpoint.py +388 -0
- fwshift/logreaders/fortios.py +177 -0
- fwshift/logreaders/iptables.py +186 -0
- fwshift/logreaders/panos.py +202 -0
- fwshift/matrix.py +950 -0
- fwshift/model.py +319 -0
- fwshift/names.py +108 -0
- fwshift/narrow.py +284 -0
- fwshift/netutil.py +155 -0
- fwshift/options.py +54 -0
- fwshift/parsers/__init__.py +5 -0
- fwshift/parsers/asa.py +1864 -0
- fwshift/parsers/aws.py +1042 -0
- fwshift/parsers/azure.py +1187 -0
- fwshift/parsers/barracuda.py +809 -0
- fwshift/parsers/checkpoint.py +1309 -0
- fwshift/parsers/cisco.py +1994 -0
- fwshift/parsers/forcepoint.py +1562 -0
- fwshift/parsers/fortios.py +1227 -0
- fwshift/parsers/gcp.py +918 -0
- fwshift/parsers/huawei.py +934 -0
- fwshift/parsers/ios.py +1660 -0
- fwshift/parsers/iptables.py +875 -0
- fwshift/parsers/junos.py +1162 -0
- fwshift/parsers/meraki.py +751 -0
- fwshift/parsers/nftables.py +2555 -0
- fwshift/parsers/nsx.py +893 -0
- fwshift/parsers/opnsense.py +1158 -0
- fwshift/parsers/panos.py +406 -0
- fwshift/parsers/pfsense.py +1677 -0
- fwshift/parsers/routeros.py +1549 -0
- fwshift/parsers/smc_dump.py +298 -0
- fwshift/parsers/sonicwall.py +814 -0
- fwshift/parsers/sophos.py +1034 -0
- fwshift/parsers/stormshield.py +884 -0
- fwshift/parsers/terraform.py +1371 -0
- fwshift/parsers/watchguard.py +527 -0
- fwshift/regions.py +167 -0
- fwshift/report.py +221 -0
- fwshift/simulate.py +152 -0
- fwshift/transform.py +1735 -0
- fwshift/usage.py +1132 -0
- fwshift/xmlread.py +43 -0
- fwshift-0.31.0.dist-info/METADATA +855 -0
- fwshift-0.31.0.dist-info/RECORD +87 -0
- fwshift-0.31.0.dist-info/WHEEL +5 -0
- fwshift-0.31.0.dist-info/entry_points.txt +2 -0
- fwshift-0.31.0.dist-info/licenses/LICENSE +674 -0
- fwshift-0.31.0.dist-info/top_level.txt +1 -0
fwshift/__init__.py
ADDED
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
|