diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in index 6398c0e07..13e7fd81d 100644 --- a/op-mode-definitions/nat.xml.in +++ b/op-mode-definitions/nat.xml.in @@ -1,99 +1,119 @@ <?xml version="1.0" encoding="UTF-8"?> <interfaceDefinition> <node name="show"> <children> <node name="nat"> <properties> <help>Show IPv4 Network Address Translation (NAT) information</help> </properties> <children> <node name="cgnat"> <properties> <help>Show Carrier-Grade Network Address Translation (CGNAT)</help> </properties> <children> <node name="allocation"> <properties> <help>Show allocated CGNAT parameters</help> </properties> + <children> + <tagNode name="external-address"> + <properties> + <help>Show CGNAT allocations for an external IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/cgnat.py show_allocation --external-address "$6"</command> + </tagNode> + <tagNode name="internal-address"> + <properties> + <help>Show CGNAT allocations for an internal IP address</help> + <completionHelp> + <list><x.x.x.x></list> + </completionHelp> + </properties> + <command>sudo ${vyos_op_scripts_dir}/cgnat.py show_allocation --internal-address "$6"</command> + </tagNode> + </children> <command>sudo ${vyos_op_scripts_dir}/cgnat.py show_allocation</command> </node> </children> </node> <node name="source"> <properties> <help>Show source IPv4 to IPv4 Network Address Translation (NAT) information</help> </properties> <children> <node name="rules"> <properties> <help>Show configured source NAT rules</help> </properties> <command>sudo ${vyos_op_scripts_dir}/nat.py show_rules --direction source --family inet</command> </node> <node name="statistics"> <properties> <help>Show statistics for configured source NAT rules</help> </properties> <command>sudo ${vyos_op_scripts_dir}/nat.py show_statistics --direction source --family inet</command> </node> <node name="translations"> <properties> <help>Show active source NAT translations</help> </properties> <children> <tagNode name="address"> <properties> <help>Show active source NAT translations for an IP address</help> <completionHelp> <list><x.x.x.x></list> </completionHelp> </properties> <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet --address "$6"</command> </tagNode> </children> <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet</command> </node> </children> </node> <node name="destination"> <properties> <help>Show destination IPv4 to IPv4 Network Address Translation (NAT) information</help> </properties> <children> <node name="rules"> <properties> <help>Show configured destination NAT rules</help> </properties> <command>sudo ${vyos_op_scripts_dir}/nat.py show_rules --direction destination --family inet</command> </node> <node name="statistics"> <properties> <help>Show statistics for configured destination NAT rules</help> </properties> <command>sudo ${vyos_op_scripts_dir}/nat.py show_statistics --direction destination --family inet</command> </node> <node name="translations"> <properties> <help>Show active destination NAT translations</help> </properties> <children> <tagNode name="address"> <properties> <help>Show active NAT destination translations for an IP address</help> <completionHelp> <list><x.x.x.x></list> </completionHelp> </properties> <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet --address "$6"</command> </tagNode> </children> <command>sudo ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet</command> </node> </children> </node> </children> </node> </children> </node> </interfaceDefinition> diff --git a/src/op_mode/cgnat.py b/src/op_mode/cgnat.py index e58b15809..9ad8f92f9 100755 --- a/src/op_mode/cgnat.py +++ b/src/op_mode/cgnat.py @@ -1,74 +1,96 @@ #!/usr/bin/env python3 # # Copyright (C) 2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # 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 json import sys +import typing from tabulate import tabulate import vyos.opmode from vyos.configquery import ConfigTreeQuery from vyos.utils.process import cmd CGNAT_TABLE = 'cgnat' -def _get_raw_data(): - """ Get CGNAT dictionary - """ +def _get_raw_data(external_address: str = '', internal_address: str = '') -> list[dict]: + """Get CGNAT dictionary and filter by external or internal address if provided.""" cmd_output = cmd(f'nft --json list table ip {CGNAT_TABLE}') data = json.loads(cmd_output) - return data - -def _get_formatted_output(data): elements = data['nftables'][2]['map']['elem'] allocations = [] for elem in elements: internal = elem[0] # internal external = elem[1]['concat'][0] # external start_port = elem[1]['concat'][1]['range'][0] end_port = elem[1]['concat'][1]['range'][1] port_range = f'{start_port}-{end_port}' - allocations.append((internal, external, port_range)) + if (internal_address and internal != internal_address) or ( + external_address and external != external_address + ): + continue + + allocations.append( + { + 'internal_address': internal, + 'external_address': external, + 'port_range': port_range, + } + ) + + return allocations + + +def _get_formatted_output(allocations: list[dict]) -> str: + # Convert the list of dictionaries to a list of tuples for tabulate headers = ['Internal IP', 'External IP', 'Port range'] - output = tabulate(allocations, headers, numalign="left") + data = [ + (alloc['internal_address'], alloc['external_address'], alloc['port_range']) + for alloc in allocations + ] + output = tabulate(data, headers, numalign="left") return output -def show_allocation(raw: bool): +def show_allocation( + raw: bool, + external_address: typing.Optional[str], + internal_address: typing.Optional[str], +) -> str: config = ConfigTreeQuery() if not config.exists('nat cgnat'): raise vyos.opmode.UnconfiguredSubsystem('CGNAT is not configured') if raw: - return _get_raw_data() + return _get_raw_data(external_address, internal_address) else: - raw_data = _get_raw_data() + raw_data = _get_raw_data(external_address, internal_address) return _get_formatted_output(raw_data) if __name__ == '__main__': try: res = vyos.opmode.run(sys.modules[__name__]) if res: print(res) except (ValueError, vyos.opmode.Error) as e: print(e) sys.exit(1)