summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xsrc/conf_mode/nat_cgnat.py110
-rwxr-xr-xsrc/op_mode/firewall.py85
-rw-r--r--src/op_mode/show_techsupport_report.py23
3 files changed, 163 insertions, 55 deletions
diff --git a/src/conf_mode/nat_cgnat.py b/src/conf_mode/nat_cgnat.py
index 34ec64fce..3484e5873 100755
--- a/src/conf_mode/nat_cgnat.py
+++ b/src/conf_mode/nat_cgnat.py
@@ -119,37 +119,34 @@ class IPOperations:
+ [self.ip_network.broadcast_address]
]
- def get_prefix_by_ip_range(self):
+ def get_prefix_by_ip_range(self) -> list[ipaddress.IPv4Network]:
"""Return the common prefix for the address range
Example:
% ip = IPOperations('100.64.0.1-100.64.0.5')
% ip.get_prefix_by_ip_range()
- 100.64.0.0/29
+ [IPv4Network('100.64.0.1/32'), IPv4Network('100.64.0.2/31'), IPv4Network('100.64.0.4/31')]
"""
- if '-' in self.ip_prefix:
- ip_start, ip_end = self.ip_prefix.split('-')
- start_ip = ipaddress.IPv4Address(ip_start.strip())
- end_ip = ipaddress.IPv4Address(ip_end.strip())
-
- start_int = int(start_ip)
- end_int = int(end_ip)
-
- # XOR to find differing bits
- xor = start_int ^ end_int
-
- # Count the number of leading zeros in the XOR result to find the prefix length
- prefix_length = 32 - xor.bit_length()
-
- # Calculate the network address
- network_int = start_int & (0xFFFFFFFF << (32 - prefix_length))
- network_address = ipaddress.IPv4Address(network_int)
+ # We do not need to convert the IP range to network
+ # if it is already in network format
+ if self.ip_network:
+ return [self.ip_network]
+
+ # Raise an error if the IP range is not in the correct format
+ if '-' not in self.ip_prefix:
+ raise ValueError(
+ 'Invalid IP range format. Please provide the IP range in CIDR format or with "-" separator.'
+ )
+ # Split the IP range and convert it to IP address objects
+ range_start, range_end = self.ip_prefix.split('-')
+ range_start = ipaddress.IPv4Address(range_start)
+ range_end = ipaddress.IPv4Address(range_end)
- return f"{network_address}/{prefix_length}"
- return self.ip_prefix
+ # Return the summarized IP networks list
+ return list(ipaddress.summarize_address_range(range_start, range_end))
-def _delete_conntrack_entries(source_prefixes: list) -> None:
+def _delete_conntrack_entries(source_prefixes: list[ipaddress.IPv4Network]) -> None:
"""Delete all conntrack entries for the list of prefixes"""
for source_prefix in source_prefixes:
run(f'conntrack -D -s {source_prefix}')
@@ -224,15 +221,31 @@ def get_config(config=None):
with_recursive_defaults=True,
)
- if conf.exists(base) and is_node_changed(conf, base + ['pool']):
- config.update({'delete_conntrack_entries': {}})
+ effective_config = conf.get_config_dict(
+ base,
+ get_first_key=True,
+ key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ effective=True,
+ )
+
+ # Check if the pool configuration has changed
+ if not conf.exists(base) or is_node_changed(conf, base + ['pool']):
+ config['delete_conntrack_entries'] = {}
+
+ # add running config
+ if effective_config:
+ config['effective'] = effective_config
+
+ if not conf.exists(base):
+ config['deleted'] = {}
return config
def verify(config):
# bail out early - looks like removal from running config
- if not config:
+ if 'deleted' in config:
return None
if 'pool' not in config:
@@ -336,7 +349,7 @@ def verify(config):
def generate(config):
- if not config:
+ if 'deleted' in config:
return None
proto_maps = []
@@ -401,13 +414,38 @@ def generate(config):
def apply(config):
- if not config:
+ if 'deleted' in config:
# Cleanup cgnat
cmd('nft delete table ip cgnat')
if os.path.isfile(nftables_cgnat_config):
os.unlink(nftables_cgnat_config)
- return None
- cmd(f'nft --file {nftables_cgnat_config}')
+ else:
+ cmd(f'nft --file {nftables_cgnat_config}')
+
+ # Delete conntrack entries
+ # if the pool configuration has changed
+ if 'delete_conntrack_entries' in config and 'effective' in config:
+ # Prepare the list of internal pool prefixes
+ internal_pool_prefix_list: list[ipaddress.IPv4Network] = []
+
+ # Get effective rules configurations
+ for rule_config in config['effective'].get('rule', {}).values():
+ # Get effective internal pool configuration
+ internal_pool = rule_config['source']['pool']
+ # Find the internal IP ranges for the internal pool
+ internal_ip_ranges: list[str] = config['effective']['pool']['internal'][
+ internal_pool
+ ]['range']
+ # Get the IP prefixes for the internal IP range
+ for internal_range in internal_ip_ranges:
+ ip_prefix: list[ipaddress.IPv4Network] = IPOperations(
+ internal_range
+ ).get_prefix_by_ip_range()
+ # Add the IP prefixes to the list of all internal pool prefixes
+ internal_pool_prefix_list += ip_prefix
+
+ # Delete required sources for conntrack
+ _delete_conntrack_entries(internal_pool_prefix_list)
# Logging allocations
if 'log_allocation' in config:
@@ -420,23 +458,11 @@ def apply(config):
external_host, port_range = rest.split(' . ')
# Log the parsed data
logger.info(
- f"Internal host: {internal_host.lstrip()}, external host: {external_host}, Port range: {port_range}")
+ f'Internal host: {internal_host.lstrip()}, external host: {external_host}, Port range: {port_range}')
except ValueError as e:
# Log error message
logger.error(f"Error processing line '{allocation}': {e}")
- # Delete conntrack entries
- if 'delete_conntrack_entries' in config:
- internal_pool_prefix_list = []
- for rule, rule_config in config['rule'].items():
- internal_pool = rule_config['source']['pool']
- internal_ip_ranges: list = config['pool']['internal'][internal_pool]['range']
- for internal_range in internal_ip_ranges:
- ip_prefix = IPOperations(internal_range).get_prefix_by_ip_range()
- internal_pool_prefix_list.append(ip_prefix)
- # Deleta required sources for conntrack
- _delete_conntrack_entries(internal_pool_prefix_list)
-
if __name__ == '__main__':
try:
diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py
index 15fbb65a2..c197ca434 100755
--- a/src/op_mode/firewall.py
+++ b/src/op_mode/firewall.py
@@ -63,10 +63,10 @@ def get_nftables_details(family, hook, priority):
aux=''
if hook == 'name' or hook == 'ipv6-name':
- command = f'sudo nft list chain {suffix} vyos_filter {name_prefix}{priority}'
+ command = f'nft list chain {suffix} vyos_filter {name_prefix}{priority}'
else:
up_hook = hook.upper()
- command = f'sudo nft list chain {suffix} vyos_filter VYOS_{aux}{up_hook}_{priority}'
+ command = f'nft list chain {suffix} vyos_filter VYOS_{aux}{up_hook}_{priority}'
try:
results = cmd(command)
@@ -90,12 +90,42 @@ def get_nftables_details(family, hook, priority):
out[rule_id] = rule
return out
+def get_nftables_state_details(family):
+ if family == 'ipv6':
+ suffix = 'ip6'
+ name_suffix = 'POLICY6'
+ elif family == 'ipv4':
+ suffix = 'ip'
+ name_suffix = 'POLICY'
+ else:
+ # no state policy for bridge
+ return {}
+
+ command = f'nft list chain {suffix} vyos_filter VYOS_STATE_{name_suffix}'
+ try:
+ results = cmd(command)
+ except:
+ return {}
+
+ out = {}
+ for line in results.split('\n'):
+ rule = {}
+ for state in ['established', 'related', 'invalid']:
+ if state in line:
+ counter_search = re.search(r'counter packets (\d+) bytes (\d+)', line)
+ if counter_search:
+ rule['packets'] = counter_search[1]
+ rule['bytes'] = counter_search[2]
+ rule['conditions'] = re.sub(r'(\b(counter packets \d+ bytes \d+|drop|reject|return|log)\b|comment "[\w\-]+")', '', line).strip()
+ out[state] = rule
+ return out
+
def get_nftables_group_members(family, table, name):
prefix = 'ip6' if family == 'ipv6' else 'ip'
out = []
try:
- results_str = cmd(f'sudo nft -j list set {prefix} {table} {name}')
+ results_str = cmd(f'nft -j list set {prefix} {table} {name}')
results = json.loads(results_str)
except:
return out
@@ -172,6 +202,34 @@ def output_firewall_name(family, hook, priority, firewall_conf, single_rule_id=N
rows[rows.index(i)].pop(1)
print(tabulate.tabulate(rows, header) + '\n')
+def output_firewall_state_policy(family):
+ if family == 'bridge':
+ return {}
+ print(f'\n---------------------------------\n{family} State Policy\n')
+
+ details = get_nftables_state_details(family)
+ rows = []
+
+ for state, state_conf in details.items():
+ row = [state, state_conf['conditions']]
+ row.append(state_conf.get('packets', 0))
+ row.append(state_conf.get('bytes', 0))
+ row.append(state_conf.get('conditions'))
+ rows.append(row)
+
+ if rows:
+ if args.rule:
+ rows.pop()
+
+ if args.detail:
+ header = ['State', 'Conditions', 'Packets', 'Bytes']
+ output_firewall_vertical(rows, header)
+ else:
+ header = ['State', 'Packets', 'Bytes', 'Conditions']
+ for i in rows:
+ rows[rows.index(i)].pop(1)
+ print(tabulate.tabulate(rows, header) + '\n')
+
def output_firewall_name_statistics(family, hook, prior, prior_conf, single_rule_id=None):
print(f'\n---------------------------------\n{family} Firewall "{hook} {prior}"\n')
@@ -305,6 +363,10 @@ def show_firewall():
return
for family in ['ipv4', 'ipv6', 'bridge']:
+ if 'global_options' in firewall:
+ if 'state_policy' in firewall['global_options']:
+ output_firewall_state_policy(family)
+
if family in firewall:
for hook, hook_conf in firewall[family].items():
for prior, prior_conf in firewall[family][hook].items():
@@ -316,12 +378,17 @@ def show_firewall_family(family):
conf = Config()
firewall = get_config_node(conf)
- if not firewall or family not in firewall:
+ if not firewall:
return
- for hook, hook_conf in firewall[family].items():
- for prior, prior_conf in firewall[family][hook].items():
- output_firewall_name(family, hook, prior, prior_conf)
+ if 'global_options' in firewall:
+ if 'state_policy' in firewall['global_options']:
+ output_firewall_state_policy(family)
+
+ if family in firewall:
+ for hook, hook_conf in firewall[family].items():
+ for prior, prior_conf in firewall[family][hook].items():
+ output_firewall_name(family, hook, prior, prior_conf)
def show_firewall_name(family, hook, priority):
print('Ruleset Information')
@@ -622,6 +689,10 @@ def show_statistics():
return
for family in ['ipv4', 'ipv6', 'bridge']:
+ if 'global_options' in firewall:
+ if 'state_policy' in firewall['global_options']:
+ output_firewall_state_policy(family)
+
if family in firewall:
for hook, hook_conf in firewall[family].items():
for prior, prior_conf in firewall[family][hook].items():
diff --git a/src/op_mode/show_techsupport_report.py b/src/op_mode/show_techsupport_report.py
index 230fb252d..32cf67778 100644
--- a/src/op_mode/show_techsupport_report.py
+++ b/src/op_mode/show_techsupport_report.py
@@ -14,10 +14,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import os
+import sys
from typing import List
-from vyos.utils.process import rc_cmd
from vyos.ifconfig import Section
from vyos.ifconfig import Interface
+from vyos.utils.process import rc_cmd
def print_header(command: str) -> None:
@@ -50,7 +52,15 @@ def execute_command(command: str, header_text: str) -> None:
print_header(header_text)
try:
rc, output = rc_cmd(command)
- print(output)
+ # Enable unbuffered print param to improve responsiveness of printed
+ # output to end user
+ print(output, flush=True)
+ # Exit gracefully when user interrupts program output
+ # Flush standard streams; redirect remaining output to devnull
+ # Resolves T5633: Bug #1 and 3
+ except (BrokenPipeError, KeyboardInterrupt):
+ os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
+ sys.exit(1)
except Exception as e:
print(f"Error executing command: {command}")
print(f"Error message: {e}")
@@ -155,13 +165,13 @@ def show_route() -> None:
"show ip route supernets-only",
"show ip route table all",
"show ip route vrf all",
- "show ipv6 route bgp | head 108",
+ "show ipv6 route bgp | head -108",
"show ipv6 route cache",
"show ipv6 route connected",
"show ipv6 route forward",
"show ipv6 route isis",
"show ipv6 route kernel",
- "show ipv6 route ospf",
+ "show ipv6 route ospfv3",
"show ipv6 route rip",
"show ipv6 route static",
"show ipv6 route summary",
@@ -179,8 +189,9 @@ def show_firewall() -> None:
def show_system() -> None:
"""Prints system parameters."""
- execute_command(op('show system image version'), 'Show System Image Version')
- execute_command(op('show system image storage'), 'Show System Image Storage')
+ execute_command(op('show version'), 'Show System Version')
+ execute_command(op('show system storage'), 'Show System Storage')
+ execute_command(op('show system image details'), 'Show System Image Details')
def show_date() -> None: