From 91647be271f0676a067a53374d1b47d0b261dc77 Mon Sep 17 00:00:00 2001 From: Rajendra Dendukuri Date: Tue, 6 Aug 2019 20:38:46 -0700 Subject: [PATCH 1/9] ZTP CLI commands Implemented following commands to use Zero Touch Provisioning show ztp status config ztp enable config ztp disable config ztp run Signed-off-by: Rajendra Dendukuri --- config/main.py | 34 ++++++++++++++++++++++++++++++++++ show/main.py | 20 ++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/config/main.py b/config/main.py index 14d2e4dfa5..e885e529bd 100755 --- a/config/main.py +++ b/config/main.py @@ -1243,6 +1243,40 @@ def naming_mode_alias(): """Set CLI interface naming mode to ALIAS (Vendor port alias)""" set_interface_naming_mode('alias') +@config.group() +def ztp(): + """ Configure Zero Touch Provisioning """ + if os.path.isfile('/usr/bin/ztp') is False: + exit("ZTP feature unavailable in this image version") + + if os.geteuid() != 0: + exit("Root privileges are required for this operation") + pass + +@ztp.command() +@click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, + expose_value=False, prompt='ZTP will be restarted. You may lose switch data and connectivity, continue?') +@click.argument('run', required=False) +def run(run): + """Restart ZTP of the device.""" + command = "ztp run -y" + run_command(command, display_cmd=True) + +@ztp.command() +@click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, + expose_value=False, prompt='Active ZTP session will be stopped and disabled, continue?') +@click.argument('disable', required=False) +def disable(disable): + """Administratively Disable ZTP.""" + command = "ztp disable -y" + run_command(command, display_cmd=True) + +@ztp.command() +@click.argument('enable', required=False) +def enable(enable): + """Administratively Enable ZTP.""" + command = "ztp enable" + run_command(command, display_cmd=True) if __name__ == '__main__': config() diff --git a/show/main.py b/show/main.py index e8a2617bf3..8f34060c45 100755 --- a/show/main.py +++ b/show/main.py @@ -1913,5 +1913,25 @@ def tablelize(keys, data, enable_table_keys, prefix): click.echo(tabulate(tablelize(keys, data, enable_table_keys, prefix), header)) state_db.close(state_db.STATE_DB) +# +# 'ztp status' command ("show ztp status") +# +@cli.command() +@click.argument('status', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def ztp(status, verbose): + """Show Zero Touch Provisioning status""" + if os.path.isfile('/usr/bin/ztp') is False: + exit("ZTP feature unavailable in this image version") + + if os.geteuid() != 0: + exit("Root privileges are required for this operation") + pass + + cmd = "ztp status" + if verbose: + cmd = cmd + " --verbose" + run_command(cmd, display_cmd=verbose) + if __name__ == '__main__': cli() From ad4060d9cf58d12a8585208e08e5eebc9699fec0 Mon Sep 17 00:00:00 2001 From: Rajendra Dendukuri Date: Tue, 3 Sep 2019 06:38:37 -0700 Subject: [PATCH 2/9] Added argument validation for ztp commands --- config/main.py | 6 +++--- show/main.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/main.py b/config/main.py index e885e529bd..0962d5e0d8 100755 --- a/config/main.py +++ b/config/main.py @@ -1256,7 +1256,7 @@ def ztp(): @ztp.command() @click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, expose_value=False, prompt='ZTP will be restarted. You may lose switch data and connectivity, continue?') -@click.argument('run', required=False) +@click.argument('run', required=False, type=click.Choice(["run"])) def run(run): """Restart ZTP of the device.""" command = "ztp run -y" @@ -1265,14 +1265,14 @@ def run(run): @ztp.command() @click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, expose_value=False, prompt='Active ZTP session will be stopped and disabled, continue?') -@click.argument('disable', required=False) +@click.argument('disable', required=False, type=click.Choice(["disable"])) def disable(disable): """Administratively Disable ZTP.""" command = "ztp disable -y" run_command(command, display_cmd=True) @ztp.command() -@click.argument('enable', required=False) +@click.argument('enable', required=False, type=click.Choice(["enable"])) def enable(enable): """Administratively Enable ZTP.""" command = "ztp enable" diff --git a/show/main.py b/show/main.py index 8f34060c45..9663bd8f4e 100755 --- a/show/main.py +++ b/show/main.py @@ -1917,7 +1917,7 @@ def tablelize(keys, data, enable_table_keys, prefix): # 'ztp status' command ("show ztp status") # @cli.command() -@click.argument('status', required=False) +@click.argument('status', required=False, type=click.Choice(["status"])) @click.option('--verbose', is_flag=True, help="Enable verbose output") def ztp(status, verbose): """Show Zero Touch Provisioning status""" From 638d9315516f1597b0d0da8d981285e3c05b713f Mon Sep 17 00:00:00 2001 From: Akhilesh Samineni Date: Mon, 16 Sep 2019 05:39:54 -0700 Subject: [PATCH 3/9] Changes in swss-utilities submodule to support NAT feature. Signed-off-by: akhilesh.samineni@broadcom.com --- clear/main.py | 25 + config/main.py | 8 +- config/nat.py | 1127 +++++++++++++++++++++++++++++++++++ scripts/dump_nat_entries.py | 22 + scripts/fast-reboot | 6 + scripts/generate_dump | 24 + scripts/natclear | 68 +++ scripts/natconfig | 386 ++++++++++++ scripts/natshow | 418 +++++++++++++ setup.py | 4 + show/main.py | 106 ++++ 11 files changed, 2193 insertions(+), 1 deletion(-) create mode 100644 config/nat.py create mode 100644 scripts/dump_nat_entries.py create mode 100644 scripts/natclear create mode 100644 scripts/natconfig create mode 100644 scripts/natshow diff --git a/clear/main.py b/clear/main.py index 6c274ae99b..84a1aef346 100755 --- a/clear/main.py +++ b/clear/main.py @@ -374,5 +374,30 @@ def line(linenum): cmd = "consutil clear " + str(linenum) run_command(cmd) +# +# 'nat' group ("clear nat ...") +# + +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def nat(): + """Clear the nat info""" + pass + +# 'statistics' subcommand ("clear nat statistics") +@nat.command() +def statistics(): + """ Clear all NAT statistics """ + + cmd = "natclear -s" + run_command(cmd) + +# 'translations' subcommand ("clear nat translations") +@nat.command() +def translations(): + """ Clear all NAT translations """ + + cmd = "natclear -t" + run_command(cmd) + if __name__ == '__main__': cli() diff --git a/config/main.py b/config/main.py index dae0c8b604..1f31b2822f 100755 --- a/config/main.py +++ b/config/main.py @@ -17,6 +17,7 @@ import aaa import mlnx +import nat CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?']) @@ -347,6 +348,7 @@ def _stop_services(): 'pmon', 'bgp', 'hostcfgd', + 'nat' ] if asic_type == 'mellanox' and 'pmon' in services_to_stop: services_to_stop.remove('pmon') @@ -375,7 +377,8 @@ def _reset_failed_services(): 'snmp', 'swss', 'syncd', - 'teamd' + 'teamd', + 'nat' ] for service in services_to_reset: @@ -398,6 +401,7 @@ def _restart_services(): 'pmon', 'lldp', 'hostcfgd', + 'nat' ] if asic_type == 'mellanox' and 'pmon' in services_to_restart: services_to_restart.remove('pmon') @@ -429,6 +433,8 @@ def config(): exit("Root privileges are required for this operation") config.add_command(aaa.aaa) config.add_command(aaa.tacacs) +# === Add NAT Configuration ========== +config.add_command(nat.nat) @config.command() @click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, diff --git a/config/nat.py b/config/nat.py new file mode 100644 index 0000000000..3cab8a7fa9 --- /dev/null +++ b/config/nat.py @@ -0,0 +1,1127 @@ +#!/usr/bin/env python -u + +import click +import socket +import netaddr +import ipaddress +from swsssdk import ConfigDBConnector +from swsssdk import SonicV2Connector + +TABLE_NAME_SEPARATOR = '|' + +def is_valid_ipv4_address(address): + """Check if the given ipv4 address is valid""" + invalid_list = ['0.0.0.0','255.255.255.255'] + try: + ip = ipaddress.IPv4Address(address) + if (ip.is_reserved) or (ip.is_multicast) or (ip.is_loopback) or (address in invalid_list): + return False + except: + return False + + return True + +def is_valid_port_address(address): + """Check if the given port address is valid""" + try: + port_address = int(address) + except ValueError: + return False + + if ((port_address < 1) or (port_address > 65535)): + return False + + return True + +def is_ipsubnet(val): + if not val: + return False + try: + netaddr.IPNetwork(val) + except: + return False + return True + +def nat_interface_name_is_valid(interface_name): + """Check if the given nat interface is valid""" + + config_db = ConfigDBConnector() + config_db.connect() + + if interface_name.startswith("Ethernet"): + interface_dict = config_db.get_table('PORT') + elif interface_name.startswith("PortChannel"): + interface_dict = config_db.get_table('PORTCHANNEL') + elif interface_name.startswith("Vlan"): + interface_dict = config_db.get_table('VLAN') + elif interface_name.startswith("Loopback"): + return True + else: + return False + + if interface_name is not None: + if not interface_dict: + return False + for interface in interface_dict.keys(): + if interface_name == interface: + return True + return False + +def isIpOverlappingWithAnyStaticEntry(ipAddress, table): + """Check if the given ipAddress is overlapping with any static entry""" + + config_db = ConfigDBConnector() + config_db.connect() + + static_dict = config_db.get_table(table) + + if not static_dict: + return False + + for key,values in static_dict.items(): + global_ip = "---" + local_ip = "---" + nat_type = "dnat" + + if table == 'STATIC_NAPT': + if isinstance(key, tuple) is False: + continue + + if (len(key) == 3): + global_ip = key[0] + else: + continue + elif table == 'STATIC_NAT': + if isinstance(key, unicode) is True: + global_ip = key + else: + continue + + local_ip = values["local_ip"] + + if "nat_type" in values: + nat_type = values["nat_type"] + + if nat_type == "snat": + global_ip = local_ip + + if global_ip == ipAddress: + return True + + return False + +def isOverlappingWithAnyDynamicEntry(ipAddress): + """Check if the given ipAddress is overlapping with any dynamic pool entry""" + + config_db = ConfigDBConnector() + config_db.connect() + + ip = int(ipaddress.IPv4Address(ipAddress)) + nat_pool_dict = config_db.get_table('NAT_POOL') + + if not nat_pool_dict: + return False + + for values in nat_pool_dict.values(): + global_ip = values["nat_ip"] + ipAddr = global_ip.split('-') + if (len(ipAddr) == 1): + startIp = int(ipaddress.IPv4Address(unicode(ipAddr[0]))) + endIp = int(ipaddress.IPv4Address(unicode(ipAddr[0]))) + else: + startIp = int(ipaddress.IPv4Address(unicode(ipAddr[0]))) + endIp = int(ipaddress.IPv4Address(unicode(ipAddr[1]))) + + if ((ip >= startIp) and (ip <= endIp)): + return True + + return False + +def getTwiceNatIdCountWithStaticEntries(twice_nat_id, table, count): + """Get the twice nat id count with static entries""" + + config_db = ConfigDBConnector() + config_db.connect() + + static_dict = config_db.get_table(table) + twice_id_count = count + + if not static_dict: + return twice_id_count + + for key,values in static_dict.items(): + twice_id = 0 + + if "twice_nat_id" in values: + twice_id = int(values["twice_nat_id"]) + else: + continue + + if twice_id == twice_nat_id: + twice_id_count += 1 + + return twice_id_count + +def getTwiceNatIdCountWithDynamicBinding(twice_nat_id, count, dynamic_key): + """Get the twice nat id count with dynamic binding""" + + config_db = ConfigDBConnector() + config_db.connect() + + nat_binding_dict = config_db.get_table('NAT_BINDINGS') + twice_id_count = count + + if not nat_binding_dict: + return twice_id_count + + for key, values in nat_binding_dict.items(): + nat_pool_data = config_db.get_entry('NAT_POOL',values["nat_pool"]) + twice_id = 0 + + if dynamic_key is not None: + if dynamic_key == key: + continue + + if not nat_pool_data: + continue + + if "twice_nat_id" in values: + if values["twice_nat_id"] == "NULL": + continue + else: + twice_id = int(values["twice_nat_id"]) + else: + continue + + if twice_id == twice_nat_id: + twice_id_count += 1 + + return twice_id_count + +############### NAT Configuration ################## + +# +# 'nat' group ('config nat ...') +# +@click.group('nat') +def nat(): + """NAT-related configuration tasks""" + pass + +# +# 'nat add' group ('config nat add ...') +# +@nat.group('add') +def add(): + """Add NAT-related configutation tasks""" + pass + +# +# 'nat remove' group ('config nat remove ...') +# +@nat.group('remove') +def remove(): + """Remove NAT-related configutation tasks""" + pass + +# +# 'nat set' group ('config nat set ...') +# +@nat.group('set') +def set(): + """Set NAT-related timeout configutation tasks""" + pass + +# +# 'nat reset' group ('config nat reset ...') +# +@nat.group('reset') +def reset(): + """Reset NAT-related timeout configutation tasks""" + pass + +# +# 'nat add static' group ('config nat add static ...') +# +@add.group('static') +def static(): + """Add Static related configutation""" + pass + +# +# 'nat add static basic' command ('config nat add static basic ') +# +@static.command('basic') +@click.pass_context +@click.argument('global_ip', metavar='', required=True) +@click.argument('local_ip', metavar='', required=True) +@click.option('-nat_type', metavar='', required=False, type=click.Choice(["snat", "dnat"]), help="Set nat type") +@click.option('-twice_nat_id', metavar='', required=False, type=click.IntRange(1, 9999), help="Set the twice nat id") +def add_basic(ctx, global_ip, local_ip, nat_type, twice_nat_id): + """Add Static NAT-related configutation""" + + # Verify the ip address format + if is_valid_ipv4_address(local_ip) is False: + ctx.fail("Given local ip address {} is invalid. Please enter a valid local ip address !!".format(local_ip)) + + if is_valid_ipv4_address(global_ip) is False: + ctx.fail("Given global ip address {} is invalid. Please enter a valid global ip address !!".format(global_ip)) + + config_db = ConfigDBConnector() + config_db.connect() + + entryFound = False + table = "STATIC_NAT" + key = global_ip + dataKey1 = 'local_ip' + dataKey2 = 'nat_type' + dataKey3 = 'twice_nat_id' + + data = config_db.get_entry(table, key) + if data: + if data[dataKey1] == local_ip: + click.echo("Trying to add static nat entry, which is already present.") + entryFound = True + + if nat_type == 'snat': + ipAddress = local_ip + else: + ipAddress = global_ip + + if isIpOverlappingWithAnyStaticEntry(ipAddress, 'STATIC_NAPT') is True: + ctx.fail("Given entry is overlapping with existing NAPT entry !!") + + if isOverlappingWithAnyDynamicEntry(ipAddress) is True: + ctx.fail("Given entry is overlapping with existing Dynamic entry !!") + + if entryFound is False: + counters_db = SonicV2Connector(host="127.0.0.1") + counters_db.connect(counters_db.COUNTERS_DB) + snat_entries = 0 + exists = counters_db.exists(counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') + if exists: + counter_entry = counters_db.get_all(counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') + if 'SNAT_ENTRIES' in counter_entry: + snat_entries = counter_entry['SNAT_ENTRIES'] + + if int(snat_entries) >= 1024: + click.echo("Max limit 1024 is reached for NAT entries, skipping adding the entry.") + entryFound = True + + if entryFound is False: + count = 0 + if twice_nat_id is not None: + count = getTwiceNatIdCountWithStaticEntries(twice_nat_id, table, count) + count = getTwiceNatIdCountWithDynamicBinding(twice_nat_id, count, None) + if count > 1: + ctx.fail("Same Twice nat id is not allowed for more than 2 entries!!") + + if nat_type is not None and twice_nat_id is not None: + config_db.set_entry(table, key, {dataKey1: local_ip, dataKey2: nat_type, dataKey3: twice_nat_id}) + elif nat_type is not None: + config_db.set_entry(table, key, {dataKey1: local_ip, dataKey2: nat_type}) + elif twice_nat_id is not None: + config_db.set_entry(table, key, {dataKey1: local_ip, dataKey3: twice_nat_id}) + else: + config_db.set_entry(table, key, {dataKey1: local_ip}) + +# +# 'nat add static tcp' command ('config nat add static tcp ') +# +@static.command('tcp') +@click.pass_context +@click.argument('global_ip', metavar='', required=True) +@click.argument('global_port', metavar='', type=click.IntRange(1, 65535), required=True) +@click.argument('local_ip', metavar='', required=True) +@click.argument('local_port', metavar='', type=click.IntRange(1, 65535), required=True) +@click.option('-nat_type', metavar='', required=False, type=click.Choice(["snat", "dnat"]), help="Set nat type") +@click.option('-twice_nat_id', metavar='', required=False, type=click.IntRange(1, 9999), help="Set the twice nat id") +def add_tcp(ctx, global_ip, global_port, local_ip, local_port, nat_type, twice_nat_id): + """Add Static TCP Protocol NAPT-related configutation""" + + # Verify the ip address format + if is_valid_ipv4_address(local_ip) is False: + ctx.fail("Given local ip address {} is invalid. Please enter a valid local ip address !!".format(local_ip)) + + if is_valid_ipv4_address(global_ip) is False: + ctx.fail("Given global ip address {} is invalid. Please enter a valid global ip address !!".format(global_ip)) + + config_db = ConfigDBConnector() + config_db.connect() + + entryFound = False + table = "STATIC_NAPT" + key = "{}|TCP|{}".format(global_ip, global_port) + dataKey1 = 'local_ip' + dataKey2 = 'local_port' + dataKey3 = 'nat_type' + dataKey4 = 'twice_nat_id' + + data = config_db.get_entry(table, key) + if data: + if data[dataKey1] == local_ip and data[dataKey2] == str(local_port): + click.echo("Trying to add static napt entry, which is already present.") + entryFound = True + + if nat_type == 'snat': + ipAddress = local_ip + else: + ipAddress = global_ip + + if isIpOverlappingWithAnyStaticEntry(ipAddress, 'STATIC_NAT') is True: + ctx.fail("Given entry is overlapping with existing NAT entry !!") + + if entryFound is False: + counters_db = SonicV2Connector(host="127.0.0.1") + counters_db.connect(counters_db.COUNTERS_DB) + snat_entries = 0 + exists = counters_db.exists(counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') + if exists: + counter_entry = counters_db.get_all(counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') + if 'SNAT_ENTRIES' in counter_entry: + snat_entries = counter_entry['SNAT_ENTRIES'] + + if int(snat_entries) >= 1024: + click.echo("Max limit 1024 is reached for NAT entries, skipping adding the entry.") + entryFound = True + + if entryFound is False: + count = 0 + if twice_nat_id is not None: + count = getTwiceNatIdCountWithStaticEntries(twice_nat_id, table, count) + count = getTwiceNatIdCountWithDynamicBinding(twice_nat_id, count) + if count > 1: + ctx.fail("Same Twice nat id is not allowed for more than 2 entries!!") + + if nat_type is not None and twice_nat_id is not None: + config_db.set_entry(table, key, {dataKey1: local_ip, dataKey2: local_port, dataKey3: nat_type, dataKey4: twice_nat_id}) + elif nat_type is not None: + config_db.set_entry(table, key, {dataKey1: local_ip, dataKey2: local_port, dataKey3: nat_type}) + elif twice_nat_id is not None: + config_db.set_entry(table, key, {dataKey1: local_ip, dataKey2: local_port, dataKey4: twice_nat_id}) + else: + config_db.set_entry(table, key, {dataKey1: local_ip, dataKey2: local_port}) + +# +# 'nat add static udp' command ('config nat add static udp ') +# +@static.command('udp') +@click.pass_context +@click.argument('global_ip', metavar='', required=True) +@click.argument('global_port', metavar='', type=click.IntRange(1, 65535), required=True) +@click.argument('local_ip', metavar='', required=True) +@click.argument('local_port', metavar='', type=click.IntRange(1, 65535), required=True) +@click.option('-nat_type', metavar='', required=False, type=click.Choice(["snat", "dnat"]), help="Set nat type") +@click.option('-twice_nat_id', metavar='', required=False, type=click.IntRange(1, 9999), help="Set the twice nat id") +def add_udp(ctx, global_ip, global_port, local_ip, local_port, nat_type, twice_nat_id): + """Add Static UDP Protocol NAPT-related configutation""" + + # Verify the ip address format + if is_valid_ipv4_address(local_ip) is False: + ctx.fail("Given local ip address {} is invalid. Please enter a valid local ip address !!".format(local_ip)) + + if is_valid_ipv4_address(global_ip) is False: + ctx.fail("Given global ip address {} is invalid. Please enter a valid global ip address !!".format(global_ip)) + + config_db = ConfigDBConnector() + config_db.connect() + + entryFound = False + table = "STATIC_NAPT" + key = "{}|UDP|{}".format(global_ip, global_port) + dataKey1 = 'local_ip' + dataKey2 = 'local_port' + dataKey3 = 'nat_type' + dataKey4 = 'twice_nat_id' + + data = config_db.get_entry(table, key) + if data: + if data[dataKey1] == local_ip and data[dataKey2] == str(local_port): + click.echo("Trying to add static napt entry, which is already present.") + entryFound = True + + if nat_type == 'snat': + ipAddress = local_ip + else: + ipAddress = global_ip + + if isIpOverlappingWithAnyStaticEntry(ipAddress, 'STATIC_NAT') is True: + ctx.fail("Given entry is overlapping with existing NAT entry !!") + + if entryFound is False: + counters_db = SonicV2Connector(host="127.0.0.1") + counters_db.connect(counters_db.COUNTERS_DB) + snat_entries = 0 + exists = counters_db.exists(counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') + if exists: + counter_entry = counters_db.get_all(counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') + if 'SNAT_ENTRIES' in counter_entry: + snat_entries = counter_entry['SNAT_ENTRIES'] + + if int(snat_entries) >= 1024: + click.echo("Max limit 1024 is reached for NAT entries, skipping adding the entry.") + entryFound = True + + if entryFound is False: + count = 0 + if twice_nat_id is not None: + count = getTwiceNatIdCountWithStaticEntries(twice_nat_id, table, count) + count = getTwiceNatIdCountWithDynamicBinding(twice_nat_id, count, None) + if count > 1: + ctx.fail("Same Twice nat id is not allowed for more than 2 entries!!") + + if nat_type is not None and twice_nat_id is not None: + config_db.set_entry(table, key, {dataKey1: local_ip, dataKey2: local_port, dataKey3: nat_type, dataKey4: twice_nat_id}) + elif nat_type is not None: + config_db.set_entry(table, key, {dataKey1: local_ip, dataKey2: local_port, dataKey3: nat_type}) + elif twice_nat_id is not None: + config_db.set_entry(table, key, {dataKey1: local_ip, dataKey2: local_port, dataKey4: twice_nat_id}) + else: + config_db.set_entry(table, key, {dataKey1: local_ip, dataKey2: local_port}) + +# +# 'nat remove static' group ('config nat remove static ...') +# +@remove.group('static') +def static(): + """Remove Static related configutation""" + pass + +# +# 'nat remove static basic' command ('config nat remove static basic ') +# +@static.command('basic') +@click.pass_context +@click.argument('global_ip', metavar='', required=True) +@click.argument('local_ip', metavar='', required=True) +def remove_basic(ctx, global_ip, local_ip): + """Remove Static NAT-related configutation""" + + # Verify the ip address format + if is_valid_ipv4_address(local_ip) is False: + ctx.fail("Given local ip address {} is invalid. Please enter a valid local ip address !!".format(local_ip)) + + if is_valid_ipv4_address(global_ip) is False: + ctx.fail("Given global ip address {} is invalid. Please enter a valid global ip address !!".format(global_ip)) + + config_db = ConfigDBConnector() + config_db.connect() + + entryFound = False + table = 'STATIC_NAT' + key = global_ip + dataKey = 'local_ip' + + data = config_db.get_entry(table, key) + if data: + if data[dataKey] == local_ip: + config_db.set_entry(table, key, None) + entryFound = True + + if entryFound is False: + click.echo("Trying to delete static nat entry, which is not present.") + + +# +# 'nat remove static tcp' command ('config nat remove static tcp ') +# +@static.command('tcp') +@click.pass_context +@click.argument('global_ip', metavar='', required=True) +@click.argument('global_port', metavar='', type=click.IntRange(1, 65535), required=True) +@click.argument('local_ip', metavar='', required=True) +@click.argument('local_port', metavar='', type=click.IntRange(1, 65535), required=True) +def remove_tcp(ctx, global_ip, global_port, local_ip, local_port): + """Remove Static TCP Protocol NAPT-related configutation""" + + # Verify the ip address format + if is_valid_ipv4_address(local_ip) is False: + ctx.fail("Given local ip address {} is invalid. Please enter a valid local ip address !!".format(local_ip)) + + if is_valid_ipv4_address(global_ip) is False: + ctx.fail("Given global ip address {} is invalid. Please enter a valid global ip address !!".format(global_ip)) + + config_db = ConfigDBConnector() + config_db.connect() + + entryFound = False + table = "STATIC_NAPT" + key = "{}|TCP|{}".format(global_ip, global_port) + dataKey1 = 'local_ip' + dataKey2 = 'local_port' + + data = config_db.get_entry(table, key) + if data: + if data['local_ip'] == local_ip and data['local_port'] == str(local_port): + config_db.set_entry(table, key, None) + entryFound = True + + if entryFound is False: + click.echo("Trying to delete static napt entry, which is not present.") + +# +# 'nat remove static udp' command ('config nat remove static udp ') +# +@static.command('udp') +@click.pass_context +@click.argument('global_ip', metavar='', required=True) +@click.argument('global_port', metavar='', type=click.IntRange(1, 65535), required=True) +@click.argument('local_ip', metavar='', required=True) +@click.argument('local_port', metavar='', type=click.IntRange(1, 65535), required=True) +def remove_udp(ctx, global_ip, global_port, local_ip, local_port): + """Remove Static UDP Protocol NAPT-related configutation""" + + # Verify the ip address format + if is_valid_ipv4_address(local_ip) is False: + ctx.fail("Given local ip address {} is invalid. Please enter a valid local ip address !!".format(local_ip)) + + if is_valid_ipv4_address(global_ip) is False: + ctx.fail("Given global ip address {} is invalid. Please enter a valid global ip address !!".format(global_ip)) + + config_db = ConfigDBConnector() + config_db.connect() + + entryFound = False + table = "STATIC_NAPT" + key = "{}|UDP|{}".format(global_ip, global_port) + dataKey1 = 'local_ip' + dataKey2 = 'local_port' + + data = config_db.get_entry(table, key) + if data: + if data[dataKey1] == local_ip and data[dataKey2] == str(local_port): + config_db.set_entry(table, key, None) + entryFound = True + + if entryFound is False: + click.echo("Trying to delete static napt entry, which is not present.") + +# +# 'nat remove static all' command ('config nat remove static all') +# +@static.command('all') +@click.pass_context +def remove_static_all(ctx): + """Remove all Static related configutation""" + + config_db = ConfigDBConnector() + config_db.connect() + + tables = ['STATIC_NAT', 'STATIC_NAPT'] + + for table_name in tables: + table_dict = config_db.get_table(table_name) + if table_dict: + for table_key_name in table_dict.keys(): + config_db.set_entry(table_name, table_key_name, None) + +# +# 'nat add pool' command ('config nat add pool ') +# +@add.command('pool') +@click.pass_context +@click.argument('pool_name', metavar='', required=True) +@click.argument('global_ip_range', metavar='', required=True) +@click.argument('global_port_range', metavar='', required=False) +def add_pool(ctx, pool_name, global_ip_range, global_port_range): + """Add Pool for Dynamic NAT-related configutation""" + + if len(pool_name) > 32: + ctx.fail("Invalid pool name. Maximum allowed pool name is 32 characters !!") + + # Verify the ip address range and format + ip_address = global_ip_range.split("-") + if len(ip_address) > 2: + ctx.fail("Given ip address range {} is invalid. Please enter a valid ip address range !!".format(global_ip_range)) + elif len(ip_address) == 2: + if is_valid_ipv4_address(ip_address[0]) is False: + ctx.fail("Given ip address {} is not valid global address. Please enter a valid ip address !!".format(ip_address[0])) + + if is_valid_ipv4_address(ip_address[1]) is False: + ctx.fail("Given ip address {} is not valid global address. Please enter a valid ip address !!".format(ip_address[1])) + + ipLowLimit = int(ipaddress.IPv4Address(ip_address[0])) + ipHighLimit = int(ipaddress.IPv4Address(ip_address[1])) + if ipLowLimit >= ipHighLimit: + ctx.fail("Given ip address range {} is invalid. Please enter a valid ip address range !!".format(global_ip_range)) + else: + if is_valid_ipv4_address(ip_address[0]) is False: + ctx.fail("Given ip address {} is not valid global address. Please enter a valid ip address !!".format(ip_address[0])) + ipLowLimit = int(ipaddress.IPv4Address(ip_address[0])) + ipHighLimit = int(ipaddress.IPv4Address(ip_address[0])) + + # Verify the port address range and format + if global_port_range is not None: + port_address = global_port_range.split("-") + + if len(port_address) > 2: + ctx.fail("Given port address range {} is invalid. Please enter a valid port address range !!".format(global_port_range)) + elif len(port_address) == 2: + if is_valid_port_address(port_address[0]) is False: + ctx.fail("Given port value {} is invalid. Please enter a valid port value !!".format(port_address[0])) + + if is_valid_port_address(port_address[1]) is False: + ctx.fail("Given port value {} is invalid. Please enter a valid port value !!".format(port_address[1])) + + portLowLimit = int(port_address[0]) + portHighLimit = int(port_address[1]) + if portLowLimit >= portHighLimit: + ctx.fail("Given port address range {} is invalid. Please enter a valid port address range !!".format(global_port_range)) + else: + if is_valid_port_address(port_address[0]) is False: + ctx.fail("Given port value {} is invalid. Please enter a valid port value !!".format(port_address[0])) + portLowLimit = int(port_address[0]) + portHighLimit = int(port_address[0]) + else: + global_port_range = "NULL" + + config_db = ConfigDBConnector() + config_db.connect() + + entryFound = False + table = "NAT_POOL" + key = pool_name + dataKey1 = 'nat_ip' + dataKey2 = 'nat_port' + + data = config_db.get_entry(table, key) + if data: + if data[dataKey1] == global_ip_range and data[dataKey2] == global_port_range: + click.echo("Trying to add pool, which is already present.") + entryFound = True + + pool_dict = config_db.get_table(table) + if len(pool_dict) == 16: + click.echo("Failed to add pool, as already reached maximum pool limit 16.") + entryFound = True + + # Verify the Ip address is overlapping with any Static NAT entry + if entryFound == False: + static_dict = config_db.get_table('STATIC_NAT') + if static_dict: + for staticKey,staticValues in static_dict.items(): + global_ip = "---" + local_ip = "---" + nat_type = "dnat" + + if isinstance(staticKey, unicode) is True: + global_ip = staticKey + else: + continue + + local_ip = staticValues["local_ip"] + + if "nat_type" in staticValues: + nat_type = staticValues["nat_type"] + + if nat_type == "snat": + global_ip = local_ip + + ipAddress = int(ipaddress.IPv4Address(unicode(global_ip))) + if (ipAddress >= ipLowLimit and ipAddress <= ipHighLimit): + ctx.fail("Given Ip address entry is overlapping with existing Static NAT entry !!") + + if entryFound == False: + config_db.set_entry(table, key, {dataKey1: global_ip_range, dataKey2 : global_port_range}) + +# +# 'nat add binding' command ('config nat add binding ') +# +@add.command('binding') +@click.pass_context +@click.argument('binding_name', metavar='', required=True) +@click.argument('pool_name', metavar='', required=True) +@click.argument('acl_name', metavar='', required=False) +@click.option('-nat_type', metavar='', required=False, type=click.Choice(["snat", "dnat"]), help="Set nat type") +@click.option('-twice_nat_id', metavar='', required=False, type=click.IntRange(1, 9999), help="Set the twice nat id") +def add_binding(ctx, binding_name, pool_name, acl_name, nat_type, twice_nat_id): + """Add Binding for Dynamic NAT-related configutation""" + + entryFound = False + table = 'NAT_BINDINGS' + key = binding_name + dataKey1 = 'access_list' + dataKey2 = 'nat_pool' + dataKey3 = 'nat_type' + dataKey4 = 'twice_nat_id' + + if acl_name is None: + acl_name = "" + + if len(binding_name) > 32: + ctx.fail("Invalid binding name. Maximum allowed binding name is 32 characters !!") + + config_db = ConfigDBConnector() + config_db.connect() + + data = config_db.get_entry(table, key) + if data: + if data[dataKey1] == acl_name and data[dataKey2] == pool_name: + click.echo("Trying to add binding, which is already present.") + entryFound = True + + binding_dict = config_db.get_table(table) + if len(binding_dict) == 16: + click.echo("Failed to add binding, as already reached maximum binding limit 16.") + entryFound = True + + if nat_type is not None: + if nat_type == "dnat": + click.echo("Ignored, DNAT is not yet suported for Binding ") + entryFound = True + else: + nat_type = "snat" + + if twice_nat_id is None: + twice_nat_id = "NULL" + + if entryFound is False: + count = 0 + if twice_nat_id is not None: + count = getTwiceNatIdCountWithStaticEntries(twice_nat_id, 'STATIC_NAT', count) + count = getTwiceNatIdCountWithStaticEntries(twice_nat_id, 'STATIC_NAPT', count) + count = getTwiceNatIdCountWithDynamicBinding(twice_nat_id, count, key) + if count > 1: + ctx.fail("Same Twice nat id is not allowed for more than 2 entries!!") + #if nat_type is not None and twice_nat_id is not None: + config_db.set_entry(table, key, {dataKey1: acl_name, dataKey2: pool_name, dataKey3: nat_type, dataKey4: twice_nat_id}) + #elif nat_type is not None: + #config_db.set_entry(table, key, {dataKey1: acl_name, dataKey2: pool_name, dataKey3: nat_type}) + #elif twice_nat_id is not None: + #config_db.set_entry(table, key, {dataKey1: acl_name, dataKey2: pool_name, dataKey4: twice_nat_id}) + #else: + #config_db.set_entry(table, key, {dataKey1: acl_name, dataKey2 : pool_name}) + +# +# 'nat remove pool' command ('config nat remove pool ') +# +@remove.command('pool') +@click.pass_context +@click.argument('pool_name', metavar='', required=True) +def remove_pool(ctx, pool_name): + """Remove Pool for Dynamic NAT-related configutation""" + + entryFound = False + table = "NAT_POOL" + key = pool_name + + if len(pool_name) > 32: + ctx.fail("Invalid pool name. Maximum allowed pool name is 32 characters !!") + + config_db = ConfigDBConnector() + config_db.connect() + + data = config_db.get_entry(table, key) + if not data: + click.echo("Trying to delete pool, which is not present.") + entryFound = True + + binding_dict = config_db.get_table('NAT_BINDINGS') + if binding_dict and entryFound == False: + for binding_name, binding_values in binding_dict.items(): + if binding_values['nat_pool'] == pool_name: + click.echo("Pool is not removed, as it is mapped to Binding {}, remove the pool binding first !!".format(binding_name)) + entryFound = True + break + + if entryFound == False: + config_db.set_entry(table, key, None) + +# +# 'nat remove pools' command ('config nat remove pools') +# +@remove.command('pools') +@click.pass_context +def remove_pools(ctx): + """Remove all Pools for Dynamic configutation""" + + config_db = ConfigDBConnector() + config_db.connect() + + entryFound = False + pool_table_name = 'NAT_POOL' + binding_table_name = 'NAT_BINDINGS' + binding_dict = config_db.get_table(binding_table_name) + pool_dict = config_db.get_table(pool_table_name) + if pool_dict: + for pool_key_name in pool_dict.keys(): + entryFound = False + for binding_name, binding_values in binding_dict.items(): + if binding_values['nat_pool'] == pool_key_name: + click.echo("Pool {} is not removed, as it is mapped to Binding {}, remove the pool binding first !!".format(pool_key_name,binding_name)) + entryFound = True + break + + if entryFound == False: + config_db.set_entry(pool_table_name, pool_key_name, None) + +# +# 'nat remove binding' command ('config nat remove binding ') +# +@remove.command('binding') +@click.pass_context +@click.argument('binding_name', metavar='', required=True) +def remove_binding(ctx, binding_name): + """Remove Binding for Dynamic NAT-related configutation""" + + entryFound = False + table = 'NAT_BINDINGS' + key = binding_name + + if len(binding_name) > 32: + ctx.fail("Invalid binding name. Maximum allowed binding name is 32 characters !!") + + config_db = ConfigDBConnector() + config_db.connect() + + data = config_db.get_entry(table, key) + if not data: + click.echo("Trying to delete binding, which is not present.") + entryFound = True + + if entryFound == False: + config_db.set_entry(table, key, None) + +# +# 'nat remove bindings' command ('config nat remove bindings') +# +@remove.command('bindings') +@click.pass_context +def remove_bindings(ctx): + """Remove all Bindings for Dynamic configutation""" + + config_db = ConfigDBConnector() + config_db.connect() + + binding_table_name = 'NAT_BINDINGS' + binding_dict = config_db.get_table(binding_table_name) + if binding_dict: + for binding_key_name in binding_dict.keys(): + config_db.set_entry(binding_table_name, binding_key_name, None) + +# +# 'nat add interface' command ('config nat add interface -nat_zone ') +# +@add.command('interface') +@click.pass_context +@click.argument('interface_name', metavar='', required=True) +@click.option('-nat_zone', metavar='', required=True, type=click.IntRange(0, 3), help="Set nat zone") +def add_interface(ctx, interface_name, nat_zone): + """Add interface related nat configuration""" + + config_db = ConfigDBConnector() + config_db.connect() + tableFound = False + + if nat_interface_name_is_valid(interface_name) is False: + ctx.fail("Interface name is invalid. Please enter a valid interface name!!") + + if interface_name.startswith("Ethernet"): + interface_table_type = "INTERFACE" + elif interface_name.startswith("PortChannel"): + interface_table_type = "PORTCHANNEL_INTERFACE" + elif interface_name.startswith("Vlan"): + interface_table_type = "VLAN_INTERFACE" + elif interface_name.startswith("Loopback"): + interface_table_type = "LOOPBACK_INTERFACE" + + interface_table_dict = config_db.get_table(interface_table_type) + + if not interface_table_dict: + ctx.fail("Interface table is not present. Please configure ip-address on {} and apply the nat zone !!".format(interface_name)) + + for interface in interface_table_dict.keys(): + if interface_name == interface: + tableFound = True + + if tableFound == False: + ctx.fail("Interface table is not present. Please configure ip-address on {} and apply the nat zone !!".format(interface_name)) + + if interface_name.startswith("Ethernet"): + config_db.mod_entry("INTERFACE", interface_name, {"nat_zone": nat_zone}) + elif interface_name.startswith("PortChannel"): + config_db.mod_entry("PORTCHANNEL_INTERFACE", interface_name, {"nat_zone": nat_zone}) + elif interface_name.startswith("Vlan"): + config_db.mod_entry("VLAN_INTERFACE", interface_name, {"nat_zone": nat_zone}) + elif interface_name.startswith("Loopback"): + config_db.mod_entry('LOOPBACK_INTERFACE', interface_name, {"nat_zone": nat_zone}) + +# +# 'nat remove interface' command ('config nat remove interface ') +# +@remove.command('interface') +@click.pass_context +@click.argument('interface_name', metavar='', required=True) +def remove_interface(ctx, interface_name): + """Remove interface related NAT configuration""" + config_db = ConfigDBConnector() + config_db.connect() + + if nat_interface_name_is_valid(interface_name) is False: + ctx.fail("Interface name is invalid. Please enter a valid interface name!!") + + if interface_name.startswith("Ethernet"): + interface_table_type = "INTERFACE" + elif interface_name.startswith("PortChannel"): + interface_table_type = "PORTCHANNEL_INTERFACE" + elif interface_name.startswith("Vlan"): + interface_table_type = "VLAN_INTERFACE" + elif interface_name.startswith("Loopback"): + interface_table_type = "LOOPBACK_INTERFACE" + + interface_table_dict = config_db.get_table(interface_table_type) + + if not interface_table_dict: + ctx.fail("Interface table is not present. Ignoring the nat zone configuartion") + + for interface in interface_table_dict.keys(): + if interface_name == interface: + tableFound = True + + if tableFound == False: + ctx.fail("Interface table is not present. Ignoring the nat zone configuration") + + nat_config = {"nat_zone": "0"} + + if interface_name.startswith("Ethernet"): + config_db.mod_entry('INTERFACE', interface_name, nat_config) + elif interface_name.startswith("PortChannel"): + config_db.mod_entry('PORTCHANNEL_INTERFACE', interface_name, nat_config) + elif interface_name.startswith("Vlan"): + config_db.mod_entry('VLAN_INTERFACE', interface_name, nat_config) + elif interface_name.startswith("Loopback"): + config_db.mod_entry('LOOPBACK_INTERFACE', interface_name, nat_config) + +# +# 'nat remove interfaces' command ('config nat remove interfaces') +# +@remove.command('interfaces') +@click.pass_context +def remove_interfaces(ctx): + """Remove all interface related NAT configuration""" + config_db = ConfigDBConnector() + config_db.connect() + + tables = ['INTERFACE', 'PORTCHANNEL_INTERFACE', 'VLAN_INTERFACE', 'LOOPBACK_INTERFACE'] + nat_config = {"nat_zone": "0"} + + for table_name in tables: + table_dict = config_db.get_table(table_name) + if table_dict: + for table_key_name in table_dict.keys(): + if isinstance(table_key_name, unicode) is False: + continue + + config_db.set_entry(table_name, table_key_name, nat_config) + +# +# 'nat feature' group ('config nat feature ') +# +@nat.group('feature') +def feature(): + """Enable or Disable the NAT feature""" + pass + +# +# 'nat feature enable' command ('config nat feature enable>') +# +@feature.command('enable') +@click.pass_context +def enable(ctx): + """Enbale the NAT feature """ + config_db = ConfigDBConnector() + config_db.connect() + config_db.mod_entry("NAT_GLOBAL", "Values", {"admin_mode": "enabled"}) + +# +# 'nat feature disable' command ('config nat feature disable>') +# +@feature.command('disable') +@click.pass_context +def disable(ctx): + """Disable the NAT feature """ + config_db = ConfigDBConnector() + config_db.connect() + config_db.mod_entry("NAT_GLOBAL", "Values", {"admin_mode": "disabled"}) + +# +# 'nat set timeout' command ('config nat set timeout ') +# +@set.command('timeout') +@click.pass_context +@click.argument('seconds', metavar='', type=click.IntRange(300, 432000), required=True) +def timeout(ctx, seconds): + """Set NAT timeout configuration""" + config_db = ConfigDBConnector() + config_db.connect() + + config_db.mod_entry("NAT_GLOBAL", "Values", {"nat_timeout": seconds}) + +# +# 'nat set tcp-timeout' command ('config nat set tcp-timeout ') +# +@set.command('tcp-timeout') +@click.pass_context +@click.argument('seconds', metavar='', type=click.IntRange(300, 432000), required=True) +def tcp_timeout(ctx, seconds): + """Set NAT TCP timeout configuration""" + config_db = ConfigDBConnector() + config_db.connect() + + config_db.mod_entry("NAT_GLOBAL", "Values", {"nat_tcp_timeout": seconds}) + +# +# 'nat set udp-timeout' command ('config nat set udp-timeout ') +# +@set.command('udp-timeout') +@click.pass_context +@click.argument('seconds', metavar='', type=click.IntRange(120, 600), required=True) +def udp_timeout(ctx, seconds): + """Set NAT UDP timeout configuration""" + config_db = ConfigDBConnector() + config_db.connect() + + config_db.mod_entry("NAT_GLOBAL", "Values", {"nat_udp_timeout": seconds}) + +# +# 'nat reset timeout' command ('config nat reset timeout') +# +@reset.command('timeout') +@click.pass_context +def timeout(ctx): + """Reset NAT timeout configuration to default value (600 seconds)""" + config_db = ConfigDBConnector() + config_db.connect() + seconds = 600 + + config_db.mod_entry("NAT_GLOBAL", "Values", {"nat_timeout": seconds}) + +# +# 'nat reset tcp-timeout' command ('config nat reset tcp-timeout') +# +@reset.command('tcp-timeout') +@click.pass_context +def tcp_timeout(ctx): + """Reset NAT TCP timeout configuration to default value (86400 seconds)""" + config_db = ConfigDBConnector() + config_db.connect() + seconds = 86400 + + config_db.mod_entry("NAT_GLOBAL", "Values", {"nat_tcp_timeout": seconds}) + +# +# 'nat reset udp-timeout' command ('config nat reset udp-timeout') +# +@reset.command('udp-timeout') +@click.pass_context +def udp_timeout(ctx): + """Reset NAT UDP timeout configuration to default value (300 seconds)""" + config_db = ConfigDBConnector() + config_db.connect() + seconds = 300 + + config_db.mod_entry("NAT_GLOBAL", "Values", {"nat_udp_timeout": seconds}) + +if __name__ == "__main__": + nat() + diff --git a/scripts/dump_nat_entries.py b/scripts/dump_nat_entries.py new file mode 100644 index 0000000000..0bd1baf155 --- /dev/null +++ b/scripts/dump_nat_entries.py @@ -0,0 +1,22 @@ +#!/usr/bin/python + +"""" +Description: dump_nat_entries.py -- dump conntrack nat entries from kernel into a file + so as to restore them during warm reboot +""" + +import sys +import subprocess + +def main(): + ctdumpcmd = 'conntrack -L -j > /host/warmboot/nat/nat_entries.dump' + p = subprocess.Popen(ctdumpcmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (output, err) = p.communicate() + rc = p.wait() + + if rc != 0: + print("Dumping conntrack entries failed") + return + +if __name__ == '__main__': + main() diff --git a/scripts/fast-reboot b/scripts/fast-reboot index e16e11f068..98d4640a64 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -410,6 +410,12 @@ docker exec -i bgp pkill -9 zebra docker exec -i bgp pkill -9 bgpd || [ $? == 1 ] debug "Stopped bgp ..." +# Kill nat docker after saving the conntrack table +debug "Stopping nat ..." +/usr/bin/dump_nat_entries.py +docker kill nat > /dev/null +debug "Stopped nat ..." + # Kill lldp, otherwise it sends informotion about reboot. # We call `docker kill lldp` to ensure the container stops as quickly as possible, # then immediately call `systemctl stop lldp` to prevent the service from diff --git a/scripts/generate_dump b/scripts/generate_dump index 14d03c0b59..390d291a58 100755 --- a/scripts/generate_dump +++ b/scripts/generate_dump @@ -142,6 +142,26 @@ save_bgp_neighbor() { done } +############################################################################### +# Dump the nat config, iptables rules and conntrack nat entries +# Globals: +# None +# Arguments: +# None +# Returns: +# None +############################################################################### +save_nat_info() { + save_cmd "iptables -t nat -nv -L" "nat.iptables" + save_cmd "conntrack -j -L" "nat.conntrack" + save_cmd "conntrack -j -L | wc" "nat.conntrackcount" + save_cmd "conntrack -L" "nat.conntrackall" + save_cmd "conntrack -L | wc" "nat.conntrackallcount" + save_cmd "show nat config" "nat.config" +} + +############################################################################### + ############################################################################### # Given list of proc files, saves proc files to tar. # Globals: @@ -350,6 +370,8 @@ main() { save_cmd "df" "df" save_cmd "dmesg" "dmesg" + save_nat_info + save_redis "0" "APP_DB" save_redis "1" "ASIC_DB" save_redis "2" "COUNTERS_DB" @@ -379,6 +401,8 @@ main() { save_cmd "bcmcmd -t5 version" "broadcom.version" save_cmd "bcmcmd -t5 soc" "broadcom.soc" save_cmd "bcmcmd -t5 ps" "broadcom.ps" + save_cmd "bcmcmd \"l3 nat_ingress show\"" "broadcom.nat.ingress" + save_cmd "bcmcmd \"l3 nat_egress show\"" "broadcom.nat.egress" fi if $GREP -qi "aboot_platform=.*arista" /host/machine.conf; then diff --git a/scripts/natclear b/scripts/natclear new file mode 100644 index 0000000000..76cd394f50 --- /dev/null +++ b/scripts/natclear @@ -0,0 +1,68 @@ +#!/usr/bin/python +""" + Script to clear nat dynamic entries from Hardware and also to clear the nat statistics + + usage: natclear [-t | -s] + arguments: + -t, --translations + -s, --statistics + +""" + +import argparse +import json +import sys +import subprocess + +from natsort import natsorted +from swsssdk import SonicV2Connector +from tabulate import tabulate + +class NatClear(object): + + def __init__(self): + super(NatClear,self).__init__() + self.db = SonicV2Connector(host="127.0.0.1") + self.db.connect(self.db.APPL_DB) + return + + def send_notification(self, op, data): + opdata = [op,data] + msg = json.dumps(opdata,separators=(',',':')) + self.db.publish('APPL_DB','FLUSHNATREQUEST', msg) + return + +def main(): + parser = argparse.ArgumentParser(description='Clear the nat information', + formatter_class=argparse.RawTextHelpFormatter, + epilog=""" + Examples: + natclear -t + natclear -s + """) + + parser.add_argument('-t', '--translations', action='store_true', help='Clear the nat translations') + parser.add_argument('-s', '--statistics', action='store_true', help='Clear the nat statistics') + + args = parser.parse_args() + + clear_translations = args.translations + clear_statistics = args.statistics + + try: + nat = NatClear() + if clear_translations: + nat.send_notification("ENTRIES", "ALL") + print "" + print("Dynamic NAT entries are cleared.") + elif clear_statistics: + nat.send_notification("STATISTICS", "ALL") + print "" + print("NAT statistics are cleared.") + except Exception as e: + print e.message + sys.exit(1) + +if __name__ == "__main__": + main() + diff --git a/scripts/natconfig b/scripts/natconfig new file mode 100644 index 0000000000..db5ea9b667 --- /dev/null +++ b/scripts/natconfig @@ -0,0 +1,386 @@ +#!/usr/bin/python + +""" + Script to show nat configuration + Example of the output: + + root@sonic:/home/admin# sudo natconfig -s + + Nat Type IP Protocol Global IP Global L4 Port Local IP Local L4 Port Twice-Nat Id + -------- ----------- ------------ -------------- ------------- ------------- ------------ + dnat all 65.55.45.5 --- 10.0.0.1 --- --- + dnat all 65.55.45.6 --- 10.0.0.2 --- --- + dnat tcp 65.55.45.7 2000 20.0.0.1 4500 1 + snat tcp 20.0.0.2 4000 65.55.45.8 1030 1 + + root@sonic:/home/admin# sudo natconfig -p + + Pool Name Global IP Range Global L4 Port Range + ------------ ------------------------- -------------------- + Pool1 65.55.45.5 100-200 + Pool2 65.55.45.6-65.55.45.8 --- + Pool3 65.55.45.10-65.55.45.15 500-1000 + + root@sonic:/home/admin# sudo natconfig -b + + Binding Name Pool Name Access-List Nat Type Twice-Nat Id + ------------ ------------ ------------ -------- ------------ + Bind1 Pool1 --- snat --- + Bind2 Pool2 1 snat 1 + Bind3 Pool3 1,2 snat -- + + root@sonic:/home/admin# sudo natconfig -g + + Admin Mode : disabled + Global Timeout : 600 + TCP Timeout : 86400 + UDP Timeout : 300 + +""" + +import argparse +import json +import sys + +from natsort import natsorted +from tabulate import tabulate +from swsssdk import ConfigDBConnector + +class NatConfig(object): + + def __init__(self): + super(NatConfig,self).__init__() + self.config_db = ConfigDBConnector() + self.config_db.connect() + return + + def fetch_static_nat(self): + """ + Fetch Static NAT config from CONFIG DB. + """ + self.static_nat_data = [] + + static_nat_dict = self.config_db.get_table('STATIC_NAT') + + if not static_nat_dict: + return + + for key,values in static_nat_dict.items(): + ip_protocol = "all" + global_ip = "---" + global_port = "---" + local_ip = "---" + local_port = "---" + nat_type = "dnat" + twice_nat_id = "---" + + if isinstance(key, unicode) is True: + global_ip = key + else: + continue + + local_ip = values["local_ip"] + + if "local_port" in values: + local_port = values["local_port"] + + if "nat_type" in values: + nat_type = values["nat_type"] + + if "twice_nat_id" in values: + twice_nat_id = values["twice_nat_id"] + + self.static_nat_data.append((nat_type,) + (ip_protocol,) + (global_ip,) + (global_port,) + (local_ip,) + (local_port,) + (twice_nat_id,)) + + self.static_nat_data.sort(key = lambda x: x[0]) + + def fetch_static_napt(self): + """ + Fetch Static NAPT config from CONFIG DB. + """ + self.static_napt_data = [] + + static_napt_dict = self.config_db.get_table('STATIC_NAPT') + + if not static_napt_dict: + return + + for key,values in static_napt_dict.items(): + ip_protocol = "all" + global_ip = "---" + global_port = "---" + local_ip = "---" + local_port = "---" + nat_type = "dnat" + twice_nat_id = "---" + + if isinstance(key, tuple) is False: + continue + + if (len(key) == 3): + global_ip = key[0] + global_port = key[2] + ip_protocol = key[1] + else: + continue + + local_ip = values["local_ip"] + + if "local_port" in values: + local_port = values["local_port"] + + if "nat_type" in values: + nat_type = values["nat_type"] + + if "twice_nat_id" in values: + twice_nat_id = values["twice_nat_id"] + + self.static_napt_data.append((nat_type,) + (ip_protocol,) + (global_ip,) + (global_port,) + (local_ip,) + (local_port,) + (twice_nat_id,)) + + self.static_napt_data.sort(key = lambda x: x[0]) + + def fetch_pool(self): + """ + Fetch NAT Pool config from CONFIG DB. + """ + self.nat_pool_data = [] + + nat_pool_dict = self.config_db.get_table('NAT_POOL') + + if not nat_pool_dict: + return + + for key,values in nat_pool_dict.items(): + pool_name = "---" + global_ip = "---" + global_port = "---" + + if isinstance(key, unicode) is True: + pool_name = key + else: + continue + + global_ip = values["nat_ip"] + + if "nat_port" in values: + if values["nat_port"] != "NULL": + global_port = values["nat_port"] + + self.nat_pool_data.append((pool_name,) + (global_ip,) + (global_port,)) + + self.nat_pool_data.sort(key = lambda x: x[0]) + + def fetch_binding(self): + """ + Fetch NAT Binding config from CONFIG DB. + """ + self.nat_binding_data = [] + + nat_binding_dict = self.config_db.get_table('NAT_BINDINGS') + + if not nat_binding_dict: + return + + for key,values in nat_binding_dict.items(): + binding_name = "---" + pool_name = "---" + access_list = "---" + nat_type = "snat" + twice_nat_id = "---" + + if isinstance(key, unicode) is True: + binding_name = key + else: + continue + + pool_name = values["nat_pool"] + + if "access_list" in values: + access_list = values["access_list"] + + if "nat_type" in values: + nat_type = values["nat_type"] + + if "twice_nat_id" in values: + if values["twice_nat_id"] != "NULL": + twice_nat_id = values["twice_nat_id"] + + self.nat_binding_data.append((binding_name,) + (pool_name,) + (access_list,) + (nat_type,) + (twice_nat_id,)) + + self.nat_binding_data.sort(key = lambda x: x[0]) + + def fetch_nat_zone(self): + """ + Fetch NAT zone config from CONFIG DB. + """ + interfaces = ['INTERFACE', 'VLAN_INTERFACE', 'PORTCHANNEL_INTERFACE', 'LOOPBACK_INTERFACE'] + + self.nat_zone_data = [] + + for i in interfaces: + interface_zone_dict = self.config_db.get_table(i) + + if not interface_zone_dict: + continue + + for key,values in interface_zone_dict.items(): + zone = "0" + + if isinstance(key, unicode) is False: + continue + + if "nat_zone" in values: + zone = values["nat_zone"] + + self.nat_zone_data.append((key,) + (zone,)) + + self.nat_zone_data.sort(key = lambda x: x[0]) + + def display_static(self): + """ + Display the static nat and napt + """ + + HEADER = ['Nat Type', 'IP Protocol', 'Global IP', 'Global Port', 'Local IP', 'Local Port', ' Twice-NAT Id'] + output = [] + + for nat in self.static_nat_data: + output.append([nat[0], nat[1], nat[2], nat[3], nat[4], nat[5], nat[6]]) + + for napt in self.static_napt_data: + output.append([napt[0], napt[1], napt[2], napt[3], napt[4], napt[5], napt[6]]) + + print "" + print tabulate(output, HEADER) + print "" + + def display_pool(self): + """ + Display the nat pool + """ + + HEADER = ['Pool Name', 'Global IP Range', 'Global Port Range'] + output = [] + + for nat in self.nat_pool_data: + output.append([nat[0], nat[1], nat[2]]) + + print "" + print tabulate(output, HEADER) + print "" + + def display_binding(self): + """ + Display the nat binding + """ + + HEADER = ['Binding Name', 'Pool Name', 'Access-List', 'Nat Type', 'Twice-NAT Id'] + output = [] + + for nat in self.nat_binding_data: + output.append([nat[0], nat[1], nat[2], nat[3], nat[4]]) + + print "" + print tabulate(output, HEADER) + print "" + + def display_global(self): + """ + Fetch NAT Global config from CONFIG DB and Display it. + """ + self.nat_global_data = [] + + global_data = self.config_db.get_entry('NAT_GLOBAL', 'Values') + if global_data: + print "" + if 'admin_mode' in global_data: + print "Admin Mode :", global_data['admin_mode'] + else: + print "Admin Mode : disabled" + if 'nat_timeout' in global_data: + print "Global Timeout :", global_data['nat_timeout'], "secs" + else: + print "Global Timeout : 600 secs" + if 'nat_tcp_timeout' in global_data: + print "TCP Timeout :", global_data['nat_tcp_timeout'], "secs" + else: + print "TCP Timeout : 86400 secs" + if 'nat_udp_timeout' in global_data: + print "UDP Timeout :", global_data['nat_udp_timeout'], "secs" + else: + print "UDP Timeout : 300 secs" + print "" + else: + print "" + print "Admin Mode : disabled" + print "Global Timeout : 600 secs" + print "TCP Timeout : 86400 secs" + print "UDP Timeout : 300 secs" + print "" + return + + def display_nat_zone(self): + """ + Display the nat zone + """ + + HEADER = ['Port', 'Zone'] + output = [] + + for nat in self.nat_zone_data: + output.append([nat[0], nat[1]]) + + print "" + print tabulate(output, HEADER) + print "" + +def main(): + parser = argparse.ArgumentParser(description='Display the nat configuration information', + formatter_class=argparse.RawTextHelpFormatter, + epilog=""" + Examples: + natconfig -s + natconfig -p + natconfig -b + natconfig -g + natconfig -z + """) + + parser.add_argument('-s', '--static', action='store_true', help='Show the nat static configuration') + parser.add_argument('-p', '--pool', action='store_true', help='Show the nat pool configuration') + parser.add_argument('-b', '--binding', action='store_true', help='Show the nat binding configuration') + parser.add_argument('-g', '--globalvalues', action='store_true', help='Show the nat global configuration') + parser.add_argument('-z', '--zones', action='store_true', help='Show the nat zone configuration') + + args = parser.parse_args() + + show_static = args.static + show_pool = args.pool + show_binding = args.binding + show_global = args.globalvalues + show_zone = args.zones + + try: + nat = NatConfig() + if show_static: + nat.fetch_static_nat() + nat.fetch_static_napt() + nat.display_static() + elif show_pool: + nat.fetch_pool() + nat.display_pool() + elif show_binding: + nat.fetch_binding() + nat.display_binding() + elif show_global: + nat.display_global() + elif show_zone: + nat.fetch_nat_zone() + nat.display_nat_zone() + except Exception as e: + print e.message + sys.exit(1) + +if __name__ == "__main__": + main() + diff --git a/scripts/natshow b/scripts/natshow new file mode 100644 index 0000000000..e02c2f1c15 --- /dev/null +++ b/scripts/natshow @@ -0,0 +1,418 @@ +#!/usr/bin/python + +""" + Script to show nat entries and nat statistics in a summary view + + Example of the output: + root@sonic:/home/admin# sudo natshow -c + + Static NAT Entries ..................... 2 + Static NAPT Entries ..................... 3 + Dynamic NAT Entries ..................... 0 + Dynamic NAPT Entries ..................... 0 + Static Twice NAT Entries ..................... 0 + Static Twice NAPT Entries ..................... 2 + Dynamic Twice NAT Entries ..................... 0 + Dynamic Twice NAPT Entries ..................... 0 + Total SNAT/SNAPT Entries ..................... 7 + Total DNAT/DNAPT Entries ..................... 0 + Total Entries ..................... 7 + + root@sonic:/home/admin# sudo natshow -t + + Static NAT Entries ..................... 2 + Static NAPT Entries ..................... 3 + Dynamic NAT Entries ..................... 0 + Dynamic NAPT Entries ..................... 0 + Static Twice NAT Entries ..................... 0 + Static Twice NAPT Entries ..................... 2 + Dynamic Twice NAT Entries ..................... 0 + Dynamic Twice NAPT Entries ..................... 0 + Total SNAT/SNAPT Entries ..................... 7 + Total DNAT/DNAPT Entries ..................... 0 + Total Entries ..................... 7 + + Protocol Source Destination Translated Source Translated Destination + -------- ----------------- ------------------ ------------------ ---------------------- + all 10.0.0.1 --- 65.55.45.5 --- + all 10.0.0.2 --- 65.55.45.6 --- + tcp 20.0.0.1:4500 --- 65.55.45.7:2000 --- + udp 20.0.0.1:4000 --- 65.55.45.7:1030 --- + tcp 20.0.0.1:6000 --- 65.55.45.7:1024 --- + udp 20.0.0.1:7000 65.55.45.8:1200 65.55.45.7:1100 20.0.0.2:8000 + tcp 20.0.0.1:6000 65.55.45.8:1500 65.55.45.7:1300 20.0.0.3:9000 + + root@sonic:/home/admin# sudo natshow -s + + Protocol Source Destination Packets Bytes + -------- ----------------- ------------------ -------- --------- + all 10.0.0.1 --- 802 1009280 + all 10.0.0.2 --- 23 5590 + tcp 20.0.0.1:4500 --- 110 12460 + udp 20.0.0.1:4000 --- 1156 789028 + tcp 20.0.0.1:6000 --- 30 34800 + udp 20.0.0.1:7000 65.55.45.8:1200 128 110204 + tcp 20.0.0.1:6000 65.55.45.8:1500 8 3806 + +""" + +import argparse +import json +import sys +import re + +from natsort import natsorted +from swsssdk import SonicV2Connector +from tabulate import tabulate + +class NatShow(object): + + def __init__(self): + super(NatShow,self).__init__() + self.asic_db = SonicV2Connector(host="127.0.0.1") + self.appl_db = SonicV2Connector(host="127.0.0.1") + self.counters_db = SonicV2Connector(host="127.0.0.1") + return + + def fetch_count(self): + """ + Fetch NAT entries count from COUNTERS DB. + """ + self.counters_db.connect(self.counters_db.COUNTERS_DB) + self.static_nat_entries = 0 + self.dynamic_nat_entries = 0 + self.static_napt_entries = 0 + self.dynamic_napt_entries = 0 + self.static_twice_nat_entries = 0 + self.dynamic_twice_nat_entries = 0 + self.static_twice_napt_entries = 0 + self.dynamic_twice_napt_entries = 0 + self.snat_entries = 0 + self.dnat_entries = 0 + + + exists = self.counters_db.exists(self.counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') + if exists: + counter_entry = self.counters_db.get_all(self.counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') + if 'STATIC_NAT_ENTRIES' in counter_entry: + self.static_nat_entries = counter_entry['STATIC_NAT_ENTRIES'] + if 'DYNAMIC_NAT_ENTRIES' in counter_entry: + self.dynamic_nat_entries = counter_entry['DYNAMIC_NAT_ENTRIES'] + if 'STATIC_NAPT_ENTRIES' in counter_entry: + self.static_napt_entries = counter_entry['STATIC_NAPT_ENTRIES'] + if 'DYNAMIC_NAPT_ENTRIES' in counter_entry: + self.dynamic_napt_entries = counter_entry['DYNAMIC_NAPT_ENTRIES'] + if 'STATIC_TWICE_NAT_ENTRIES' in counter_entry: + self.static_twice_nat_entries = counter_entry['STATIC_TWICE_NAT_ENTRIES'] + if 'DYNAMIC_TWICE_NAT_ENTRIES' in counter_entry: + self.dynamic_twice_nat_entries = counter_entry['DYNAMIC_TWICE_NAT_ENTRIES'] + if 'STATIC_TWICE_NAPT_ENTRIES' in counter_entry: + self.static_twice_napt_entries = counter_entry['STATIC_TWICE_NAPT_ENTRIES'] + if 'DYNAMIC_TWICE_NAPT_ENTRIES' in counter_entry: + self.dynamic_twice_napt_entries = counter_entry['DYNAMIC_TWICE_NAPT_ENTRIES'] + if 'SNAT_ENTRIES' in counter_entry: + self.snat_entries = counter_entry['SNAT_ENTRIES'] + if 'DNAT_ENTRIES' in counter_entry: + self.dnat_entries = counter_entry['DNAT_ENTRIES'] + + def fetch_translations(self): + """ + Fetch NAT entries from ASIC DB. + """ + self.asic_db.connect(self.asic_db.ASIC_DB) + self.nat_entries_list = [] + + nat_str = self.asic_db.keys('ASIC_DB', "ASIC_STATE:SAI_OBJECT_TYPE_NAT_ENTRY:*") + if not nat_str: + return + + for s in nat_str: + nat_entry = s.decode() + nat = json.loads(nat_entry .split(":", 2)[-1]) + if not nat: + continue + + ip_protocol = "all" + source = "---" + destination = "---" + translated_dst = "---" + translated_src = "---" + + ent = self.asic_db.get_all('ASIC_DB', s, blocking=True) + + nat_type = ent[b"SAI_NAT_ENTRY_ATTR_NAT_TYPE"] + + if nat_type == "SAI_NAT_TYPE_DESTINATION_NAT": + translated_dst_ip = ent[b"SAI_NAT_ENTRY_ATTR_DST_IP"] + if "SAI_NAT_ENTRY_ATTR_L4_DST_PORT" in ent: + translated_dst_port = ent[b"SAI_NAT_ENTRY_ATTR_L4_DST_PORT"] + translated_dst = translated_dst_ip + ":" + translated_dst_port + else: + translated_dst = translated_dst_ip + elif nat_type == "SAI_NAT_TYPE_SOURCE_NAT": + translated_src_ip = ent[b"SAI_NAT_ENTRY_ATTR_SRC_IP"] + if "SAI_NAT_ENTRY_ATTR_L4_SRC_PORT" in ent: + translated_src_port = ent[b"SAI_NAT_ENTRY_ATTR_L4_SRC_PORT"] + translated_src = translated_src_ip + ":" + translated_src_port + else: + translated_src = translated_src_ip + elif nat_type == "SAI_NAT_TYPE_DOUBLE_NAT": + translated_dst_ip = ent[b"SAI_NAT_ENTRY_ATTR_DST_IP"] + if "SAI_NAT_ENTRY_ATTR_L4_DST_PORT" in ent: + translated_dst_port = ent[b"SAI_NAT_ENTRY_ATTR_L4_DST_PORT"] + translated_dst = translated_dst_ip + ":" + translated_dst_port + else: + translated_dst = translated_dst_ip + + translated_src_ip = ent[b"SAI_NAT_ENTRY_ATTR_SRC_IP"] + if "SAI_NAT_ENTRY_ATTR_L4_SRC_PORT" in ent: + translated_src_port = ent[b"SAI_NAT_ENTRY_ATTR_L4_SRC_PORT"] + translated_src = translated_src_ip + ":" + translated_src_port + else: + translated_src = translated_src_ip + + source_ip = nat["key.src_ip"] + destination_ip = nat["key.dst_ip"] + source_port = nat["key.l4_src_port"] + destination_port = nat["key.l4_dst_port"] + protocol = nat["key.proto"] + + if (source_ip == "0.0.0.0"): + source_ip = "---" + + if (destination_ip == "0.0.0.0"): + destination_ip = "---" + + if (source_port != "0"): + source = source_ip + ":" + source_port + else: + source = source_ip + + if (destination_port != "0"): + destination = destination_ip + ":" + destination_port + else: + destination = destination_ip + + if (protocol == "6"): + ip_protocol = "tcp" + elif (protocol == "17"): + ip_protocol = "udp" + + self.nat_entries_list.append((ip_protocol,) + (source,) + (destination,) + (translated_src,) + (translated_dst,)) + + self.nat_entries_list.sort(key = lambda x: x[0]) + return + + def fetch_statistics(self): + """ + Fetch NAT statistics from Counters DB. + """ + self.appl_db.connect(self.appl_db.APPL_DB) + self.counters_db.connect(self.counters_db.COUNTERS_DB) + self.nat_statistics_list = [] + + nat_table_keys = self.appl_db.keys(self.appl_db.APPL_DB, "NAT_TABLE:*") + if nat_table_keys: + for i in nat_table_keys: + nat_entry = re.split(':', i, maxsplit=1)[-1].strip() + if nat_entry: + exists = self.counters_db.exists(self.counters_db.COUNTERS_DB, 'COUNTERS_NAT:{}'.format(nat_entry)) + + if not exists: + continue + + nat_keys = re.split(':', nat_entry) + nat_values = self.appl_db.get_all(self.appl_db.APPL_DB,'NAT_TABLE:{}'.format(nat_entry)) + + ip_protocol = "all" + source = "---" + destination = "---" + + if nat_values['nat_type'] == "snat": + source = nat_keys[0] + else: + destination = nat_keys[0] + + counter_entry = self.counters_db.get_all(self.counters_db.COUNTERS_DB, 'COUNTERS_NAT:{}'.format(nat_entry)) + packets = counter_entry['NAT_TRANSLATIONS_PKTS'] + byte = counter_entry['NAT_TRANSLATIONS_BYTES'] + + self.nat_statistics_list.append((ip_protocol,) + (source,) + (destination,) + (packets,) + (byte,)) + + napt_table_keys = self.appl_db.keys(self.appl_db.APPL_DB, "NAPT_TABLE:*") + if napt_table_keys: + for i in napt_table_keys: + napt_entry = re.split(':', i, maxsplit=1)[-1].strip() + if napt_entry: + exists = self.counters_db.exists(self.counters_db.COUNTERS_DB, 'COUNTERS_NAPT:{}'.format(napt_entry)) + + if not exists: + continue + + napt_keys = re.split(':', napt_entry) + napt_values = self.appl_db.get_all(self.appl_db.APPL_DB,'NAPT_TABLE:{}'.format(napt_entry)) + + ip_protocol = napt_keys[0] + source = "---" + destination = "---" + + if napt_values['nat_type'] == "snat": + source = napt_keys[1] + ':' + napt_keys[2] + else: + destination = napt_keys[1] + ':' + napt_keys[2] + + counter_entry = self.counters_db.get_all(self.counters_db.COUNTERS_DB, 'COUNTERS_NAPT:{}'.format(napt_entry)) + packets = counter_entry['NAT_TRANSLATIONS_PKTS'] + byte = counter_entry['NAT_TRANSLATIONS_BYTES'] + + self.nat_statistics_list.append((ip_protocol,) + (source,) + (destination,) + (packets,) + (byte,)) + + nat_twice_table_keys = self.appl_db.keys(self.appl_db.APPL_DB, "NAT_TWICE_TABLE:*") + if nat_twice_table_keys: + for i in nat_twice_table_keys: + nat_twice_entry = re.split(':', i, maxsplit=1)[-1].strip() + if nat_twice_entry: + exists = self.counters_db.exists(self.counters_db.COUNTERS_DB, 'COUNTERS_TWICE_NAT:{}'.format(nat_twice_entry)) + + if not exists: + continue + + nat_twice_keys = re.split(':', nat_twice_entry) + nat_twice_values = self.appl_db.get_all(self.appl_db.APPL_DB,'NAT_TWICE_TABLE:{}'.format(nat_twice_entry)) + + ip_protocol = "all" + source = "---" + destination = "---" + + source = nat_twice_keys[0] + destination = nat_twice_keys[1] + + counter_entry = self.counters_db.get_all(self.counters_db.COUNTERS_DB, 'COUNTERS_TWICE_NAT:{}'.format(nat_twice_entry)) + packets = counter_entry['NAT_TRANSLATIONS_PKTS'] + byte = counter_entry['NAT_TRANSLATIONS_BYTES'] + + self.nat_statistics_list.append((ip_protocol,) + (source,) + (destination,) + (packets,) + (byte,)) + + napt_twice_table_keys = self.appl_db.keys(self.appl_db.APPL_DB, "NAPT_TWICE_TABLE:*") + if napt_twice_table_keys: + for i in napt_twice_table_keys: + napt_twice_entry = re.split(':', i, maxsplit=1)[-1].strip() + if napt_twice_entry: + exists = self.counters_db.exists(self.counters_db.COUNTERS_DB, 'COUNTERS_TWICE_NAPT:{}'.format(napt_twice_entry)) + + if not exists: + continue + + napt_twice_keys = re.split(':', napt_twice_entry) + napt_twice_values = self.appl_db.get_all(self.appl_db.APPL_DB,'NAPT_TWICE_TABLE:{}'.format(napt_twice_entry)) + + ip_protocol = napt_twice_keys[0] + source = "---" + destination = "---" + + source = napt_twice_keys[1] + ':' + napt_twice_keys[2] + destination = napt_twice_keys[3] + ':' + napt_twice_keys[4] + + counter_entry = self.counters_db.get_all(self.counters_db.COUNTERS_DB, 'COUNTERS_TWICE_NAPT:{}'.format(napt_twice_entry)) + packets = counter_entry['NAT_TRANSLATIONS_PKTS'] + byte = counter_entry['NAT_TRANSLATIONS_BYTES'] + + self.nat_statistics_list.append((ip_protocol,) + (source,) + (destination,) + (packets,) + (byte,)) + + self.nat_statistics_list.sort(key = lambda x: x[0]) + return + + def display_count(self): + """ + Display the nat entries count + """ + + totalEntries = int(self.static_nat_entries) + int(self.dynamic_nat_entries) + int(self.static_napt_entries) + int(self.dynamic_napt_entries) + totalEntries += (int(self.static_twice_nat_entries) + int(self.dynamic_twice_nat_entries) + int(self.static_twice_napt_entries) + int(self.dynamic_twice_napt_entries)) + + print "" + print "Static NAT Entries ..................... {}".format(self.static_nat_entries) + print "Static NAPT Entries ..................... {}".format(self.static_napt_entries) + print "Dynamic NAT Entries ..................... {}".format(self.dynamic_nat_entries) + print "Dynamic NAPT Entries ..................... {}".format(self.dynamic_napt_entries) + print "Static Twice NAT Entries ..................... {}".format(self.static_twice_nat_entries) + print "Static Twice NAPT Entries ..................... {}".format(self.static_twice_napt_entries) + print "Dynamic Twice NAT Entries ..................... {}".format(self.dynamic_twice_nat_entries) + print "Dynamic Twice NAPT Entries ..................... {}".format(self.dynamic_twice_napt_entries) + print "Total SNAT/SNAPT Entries ..................... {}".format(self.snat_entries) + print "Total DNAT/DNAPT Entries ..................... {}".format(self.dnat_entries) + print "Total Entries ..................... {}".format(totalEntries) + print "" + + def display_translations(self): + """ + Display the nat transactions + """ + + HEADER = ['Protocol', 'Source', 'Destination', 'Translated Source', 'Translated Destination'] + output = [] + + for nat in self.nat_entries_list: + output.append([nat[0], nat[1], nat[2], nat[3], nat[4]]) + + print tabulate(output, HEADER) + print "" + + def display_statistics(self): + """ + Display the nat statistics + """ + + HEADER = ['Protocol', 'Source', 'Destination', 'Packets', 'Bytes'] + output = [] + + for nat in self.nat_statistics_list: + output.append([nat[0], nat[1], nat[2], nat[3], nat[4]]) + + print "" + print tabulate(output, HEADER) + print "" + +def main(): + parser = argparse.ArgumentParser(description='Display the nat information', + formatter_class=argparse.RawTextHelpFormatter, + epilog=""" + Examples: + natshow -t + natshow -s + natshow -c + """) + + parser.add_argument('-t', '--translations', action='store_true', help='Show the nat translations') + parser.add_argument('-s', '--statistics', action='store_true', help='Show the nat statistics') + parser.add_argument('-c', '--count', action='store_true', help='Show the nat translations count') + + args = parser.parse_args() + + show_translations = args.translations + show_statistics = args.statistics + show_count = args.count + + try: + if show_translations: + nat = NatShow() + nat.fetch_count() + nat.fetch_translations() + nat.display_count() + nat.display_translations() + elif show_statistics: + nat = NatShow() + nat.fetch_statistics() + nat.display_statistics() + elif show_count: + nat = NatShow() + nat.fetch_count() + nat.display_count() + + except Exception as e: + print e.message + sys.exit(1) + +if __name__ == "__main__": + main() + diff --git a/setup.py b/setup.py index 79ee605532..50fc1551ec 100644 --- a/setup.py +++ b/setup.py @@ -59,6 +59,7 @@ 'scripts/db_migrator.py', 'scripts/decode-syseeprom', 'scripts/dropcheck', + 'scripts/dump_nat_entries.py' 'scripts/ecnconfig', 'scripts/fast-reboot', 'scripts/fast-reboot-dump.py', @@ -69,6 +70,9 @@ 'scripts/intfstat', 'scripts/lldpshow', 'scripts/mmuconfig', + 'scripts/natclear', + 'scripts/natconfig', + 'scripts/natshow', 'scripts/nbrshow', 'scripts/neighbor_advertiser', 'scripts/pcmping', diff --git a/show/main.py b/show/main.py index e94cae45ae..3c9560bcce 100755 --- a/show/main.py +++ b/show/main.py @@ -2146,5 +2146,111 @@ def tablelize(keys, data, enable_table_keys, prefix): click.echo(tabulate(tablelize(keys, data, enable_table_keys, prefix), header)) state_db.close(state_db.STATE_DB) +# +# 'nat' group ("show nat ...") +# + +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def nat(): + """Show details of the nat """ + pass + +# 'statistics' subcommand ("show nat statistics") +@nat.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def statistics(verbose): + """ Show NAT statistics """ + + cmd = "sudo natshow -s" + run_command(cmd, display_cmd=verbose) + +# 'translations' subcommand ("show nat translations") +@nat.group(invoke_without_command=True) +@click.pass_context +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def translations(ctx, verbose): + """ Show NAT translations """ + + if ctx.invoked_subcommand is None: + cmd = "sudo natshow -t" + run_command(cmd, display_cmd=verbose) + +# 'count' subcommand ("show nat translations count") +@translations.command() +def count(): + """ Show NAT translations count """ + + cmd = "sudo natshow -c" + run_command(cmd) + +# 'config' subcommand ("show nat config") +@nat.group(invoke_without_command=True) +@click.pass_context +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def config(ctx, verbose): + """Show NAT config related information""" + if ctx.invoked_subcommand is None: + click.echo("\nGlobal Values") + cmd = "sudo natconfig -g" + run_command(cmd, display_cmd=verbose) + click.echo("Static Entries") + cmd = "sudo natconfig -s" + run_command(cmd, display_cmd=verbose) + click.echo("Pool Entries") + cmd = "sudo natconfig -p" + run_command(cmd, display_cmd=verbose) + click.echo("NAT Bindings") + cmd = "sudo natconfig -b" + run_command(cmd, display_cmd=verbose) + click.echo("NAT Zones") + cmd = "sudo natconfig -z" + run_command(cmd, display_cmd=verbose) + +# 'static' subcommand ("show nat config static") +@config.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def static(verbose): + """Show static NAT configuration""" + + cmd = "sudo natconfig -s" + run_command(cmd, display_cmd=verbose) + +# 'pool' subcommand ("show nat config pool") +@config.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def pool(verbose): + """Show NAT Pool configuration""" + + cmd = "sudo natconfig -p" + run_command(cmd, display_cmd=verbose) + + +# 'bindings' subcommand ("show nat config bindings") +@config.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def bindings(verbose): + """Show NAT binding configuration""" + + cmd = "sudo natconfig -b" + run_command(cmd, display_cmd=verbose) + +# 'globalvalues' subcommand ("show nat config globalvalues") +@config.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def globalvalues(verbose): + """Show NAT Global configuration""" + + cmd = "sudo natconfig -g" + run_command(cmd, display_cmd=verbose) + +# 'zones' subcommand ("show nat config zones") +@config.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def zones(verbose): + """Show NAT Zone configuration""" + + cmd = "sudo natconfig -z" + run_command(cmd, display_cmd=verbose) + if __name__ == '__main__': cli() From 9745e8f006d65e031037c205b5acd5945a57818b Mon Sep 17 00:00:00 2001 From: Akhilesh Samineni Date: Wed, 18 Sep 2019 00:16:02 -0700 Subject: [PATCH 4/9] Corrected changes in setup.py file. Signed-off-by: Akhilesh Samineni --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 50fc1551ec..d23a9a5f38 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ 'scripts/db_migrator.py', 'scripts/decode-syseeprom', 'scripts/dropcheck', - 'scripts/dump_nat_entries.py' + 'scripts/dump_nat_entries.py', 'scripts/ecnconfig', 'scripts/fast-reboot', 'scripts/fast-reboot-dump.py', From 59bcb26779f0c724e273196a4fa7580814b27012 Mon Sep 17 00:00:00 2001 From: Akhilesh Samineni Date: Tue, 8 Oct 2019 23:52:53 -0700 Subject: [PATCH 5/9] Minor changes to nat/py file. Signed-off-by: Akhilesh Samineni --- config/nat.py | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/config/nat.py b/config/nat.py index 3cab8a7fa9..ce56cda117 100644 --- a/config/nat.py +++ b/config/nat.py @@ -389,7 +389,7 @@ def add_tcp(ctx, global_ip, global_port, local_ip, local_port, nat_type, twice_n count = 0 if twice_nat_id is not None: count = getTwiceNatIdCountWithStaticEntries(twice_nat_id, table, count) - count = getTwiceNatIdCountWithDynamicBinding(twice_nat_id, count) + count = getTwiceNatIdCountWithDynamicBinding(twice_nat_id, count, None) if count > 1: ctx.fail("Same Twice nat id is not allowed for more than 2 entries!!") @@ -783,14 +783,8 @@ def add_binding(ctx, binding_name, pool_name, acl_name, nat_type, twice_nat_id): count = getTwiceNatIdCountWithDynamicBinding(twice_nat_id, count, key) if count > 1: ctx.fail("Same Twice nat id is not allowed for more than 2 entries!!") - #if nat_type is not None and twice_nat_id is not None: + config_db.set_entry(table, key, {dataKey1: acl_name, dataKey2: pool_name, dataKey3: nat_type, dataKey4: twice_nat_id}) - #elif nat_type is not None: - #config_db.set_entry(table, key, {dataKey1: acl_name, dataKey2: pool_name, dataKey3: nat_type}) - #elif twice_nat_id is not None: - #config_db.set_entry(table, key, {dataKey1: acl_name, dataKey2: pool_name, dataKey4: twice_nat_id}) - #else: - #config_db.set_entry(table, key, {dataKey1: acl_name, dataKey2 : pool_name}) # # 'nat remove pool' command ('config nat remove pool ') @@ -937,14 +931,7 @@ def add_interface(ctx, interface_name, nat_zone): if tableFound == False: ctx.fail("Interface table is not present. Please configure ip-address on {} and apply the nat zone !!".format(interface_name)) - if interface_name.startswith("Ethernet"): - config_db.mod_entry("INTERFACE", interface_name, {"nat_zone": nat_zone}) - elif interface_name.startswith("PortChannel"): - config_db.mod_entry("PORTCHANNEL_INTERFACE", interface_name, {"nat_zone": nat_zone}) - elif interface_name.startswith("Vlan"): - config_db.mod_entry("VLAN_INTERFACE", interface_name, {"nat_zone": nat_zone}) - elif interface_name.startswith("Loopback"): - config_db.mod_entry('LOOPBACK_INTERFACE', interface_name, {"nat_zone": nat_zone}) + config_db.mod_entry(interface_table_type, interface_name, {"nat_zone": nat_zone}) # # 'nat remove interface' command ('config nat remove interface ') @@ -981,16 +968,7 @@ def remove_interface(ctx, interface_name): if tableFound == False: ctx.fail("Interface table is not present. Ignoring the nat zone configuration") - nat_config = {"nat_zone": "0"} - - if interface_name.startswith("Ethernet"): - config_db.mod_entry('INTERFACE', interface_name, nat_config) - elif interface_name.startswith("PortChannel"): - config_db.mod_entry('PORTCHANNEL_INTERFACE', interface_name, nat_config) - elif interface_name.startswith("Vlan"): - config_db.mod_entry('VLAN_INTERFACE', interface_name, nat_config) - elif interface_name.startswith("Loopback"): - config_db.mod_entry('LOOPBACK_INTERFACE', interface_name, nat_config) + config_db.mod_entry(interface_table_type, interface_name, {"nat_zone": "0"}) # # 'nat remove interfaces' command ('config nat remove interfaces') From a3273d518eea0b690792233d86489e863cf66de4 Mon Sep 17 00:00:00 2001 From: Akhilesh Samineni Date: Tue, 15 Oct 2019 03:46:08 -0700 Subject: [PATCH 6/9] Removed space to rerun the test --- config/nat.py | 1 - 1 file changed, 1 deletion(-) diff --git a/config/nat.py b/config/nat.py index ce56cda117..80f7a8ca0a 100644 --- a/config/nat.py +++ b/config/nat.py @@ -199,7 +199,6 @@ def getTwiceNatIdCountWithDynamicBinding(twice_nat_id, count, dynamic_key): return twice_id_count ############### NAT Configuration ################## - # # 'nat' group ('config nat ...') # From 72a45b5bc9857aa776eca9b8c0488a82863701bb Mon Sep 17 00:00:00 2001 From: Akhilesh Samineni Date: Mon, 11 Nov 2019 23:44:04 -0800 Subject: [PATCH 7/9] Addressed few review comments. Signed-off-by: Akhilesh Samineni --- config/nat.py | 60 +++++++++++++++++---------------------------- scripts/fast-reboot | 13 +++++----- 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/config/nat.py b/config/nat.py index 80f7a8ca0a..b77b0885b3 100644 --- a/config/nat.py +++ b/config/nat.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python -u +#!/usr/bin/env python import click import socket @@ -7,8 +7,6 @@ from swsssdk import ConfigDBConnector from swsssdk import SonicV2Connector -TABLE_NAME_SEPARATOR = '|' - def is_valid_ipv4_address(address): """Check if the given ipv4 address is valid""" invalid_list = ['0.0.0.0','255.255.255.255'] @@ -27,19 +25,10 @@ def is_valid_port_address(address): port_address = int(address) except ValueError: return False - - if ((port_address < 1) or (port_address > 65535)): - return False - - return True -def is_ipsubnet(val): - if not val: - return False - try: - netaddr.IPNetwork(val) - except: + if port_address not in xrange(1, 65535): return False + return True def nat_interface_name_is_valid(interface_name): @@ -62,9 +51,8 @@ def nat_interface_name_is_valid(interface_name): if interface_name is not None: if not interface_dict: return False - for interface in interface_dict.keys(): - if interface_name == interface: - return True + return interface_name in interface_dict + return False def isIpOverlappingWithAnyStaticEntry(ipAddress, table): @@ -199,6 +187,7 @@ def getTwiceNatIdCountWithDynamicBinding(twice_nat_id, count, dynamic_key): return twice_id_count ############### NAT Configuration ################## + # # 'nat' group ('config nat ...') # @@ -297,13 +286,16 @@ def add_basic(ctx, global_ip, local_ip, nat_type, twice_nat_id): counters_db = SonicV2Connector(host="127.0.0.1") counters_db.connect(counters_db.COUNTERS_DB) snat_entries = 0 + max_entries = 0 exists = counters_db.exists(counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') if exists: counter_entry = counters_db.get_all(counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') if 'SNAT_ENTRIES' in counter_entry: snat_entries = counter_entry['SNAT_ENTRIES'] + if 'MAX_NAT_ENTRIES' in counter_entry: + max_entries = counter_entry['MAX_NAT_ENTRIES'] - if int(snat_entries) >= 1024: + if int(snat_entries) >= int(max_entries): click.echo("Max limit 1024 is reached for NAT entries, skipping adding the entry.") entryFound = True @@ -374,13 +366,16 @@ def add_tcp(ctx, global_ip, global_port, local_ip, local_port, nat_type, twice_n counters_db = SonicV2Connector(host="127.0.0.1") counters_db.connect(counters_db.COUNTERS_DB) snat_entries = 0 + max_entries = 0 exists = counters_db.exists(counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') if exists: counter_entry = counters_db.get_all(counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') if 'SNAT_ENTRIES' in counter_entry: snat_entries = counter_entry['SNAT_ENTRIES'] + if 'MAX_NAT_ENTRIES' in counter_entry: + max_entries = counter_entry['MAX_NAT_ENTRIES'] - if int(snat_entries) >= 1024: + if int(snat_entries) >= int(max_entries): click.echo("Max limit 1024 is reached for NAT entries, skipping adding the entry.") entryFound = True @@ -451,13 +446,16 @@ def add_udp(ctx, global_ip, global_port, local_ip, local_port, nat_type, twice_n counters_db = SonicV2Connector(host="127.0.0.1") counters_db.connect(counters_db.COUNTERS_DB) snat_entries = 0 + max_entries = 0 exists = counters_db.exists(counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') if exists: counter_entry = counters_db.get_all(counters_db.COUNTERS_DB, 'COUNTERS_GLOBAL_NAT:Values') if 'SNAT_ENTRIES' in counter_entry: snat_entries = counter_entry['SNAT_ENTRIES'] - - if int(snat_entries) >= 1024: + if 'MAX_NAT_ENTRIES' in counter_entry: + max_entries = counter_entry['MAX_NAT_ENTRIES'] + + if int(snat_entries) >= int(max_entries): click.echo("Max limit 1024 is reached for NAT entries, skipping adding the entry.") entryFound = True @@ -904,7 +902,6 @@ def add_interface(ctx, interface_name, nat_zone): config_db = ConfigDBConnector() config_db.connect() - tableFound = False if nat_interface_name_is_valid(interface_name) is False: ctx.fail("Interface name is invalid. Please enter a valid interface name!!") @@ -920,16 +917,9 @@ def add_interface(ctx, interface_name, nat_zone): interface_table_dict = config_db.get_table(interface_table_type) - if not interface_table_dict: + if not interface_table_dict or interface_name not in interface_table_dict: ctx.fail("Interface table is not present. Please configure ip-address on {} and apply the nat zone !!".format(interface_name)) - for interface in interface_table_dict.keys(): - if interface_name == interface: - tableFound = True - - if tableFound == False: - ctx.fail("Interface table is not present. Please configure ip-address on {} and apply the nat zone !!".format(interface_name)) - config_db.mod_entry(interface_table_type, interface_name, {"nat_zone": nat_zone}) # @@ -957,14 +947,7 @@ def remove_interface(ctx, interface_name): interface_table_dict = config_db.get_table(interface_table_type) - if not interface_table_dict: - ctx.fail("Interface table is not present. Ignoring the nat zone configuartion") - - for interface in interface_table_dict.keys(): - if interface_name == interface: - tableFound = True - - if tableFound == False: + if not interface_table_dict or interface_name not in interface_table_dict: ctx.fail("Interface table is not present. Ignoring the nat zone configuration") config_db.mod_entry(interface_table_type, interface_name, {"nat_zone": "0"}) @@ -1006,6 +989,7 @@ def feature(): @click.pass_context def enable(ctx): """Enbale the NAT feature """ + config_db = ConfigDBConnector() config_db.connect() config_db.mod_entry("NAT_GLOBAL", "Values", {"admin_mode": "enabled"}) diff --git a/scripts/fast-reboot b/scripts/fast-reboot index 98d4640a64..e7d4e1b1de 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -399,6 +399,13 @@ if [[ "$REBOOT_TYPE" = "warm-reboot" || "$REBOOT_TYPE" = "fastfast-reboot" ]]; t fi fi +# Kill nat docker after saving the conntrack table +debug "Stopping nat ..." +/usr/bin/dump_nat_entries.py +docker kill nat > /dev/null || true +systemctl stop nat +debug "Stopped nat ..." + # Kill radv before stopping BGP service to prevent annoucing our departure. debug "Stopping radv ..." docker kill radv &>/dev/null || [ $? == 1 ] @@ -410,12 +417,6 @@ docker exec -i bgp pkill -9 zebra docker exec -i bgp pkill -9 bgpd || [ $? == 1 ] debug "Stopped bgp ..." -# Kill nat docker after saving the conntrack table -debug "Stopping nat ..." -/usr/bin/dump_nat_entries.py -docker kill nat > /dev/null -debug "Stopped nat ..." - # Kill lldp, otherwise it sends informotion about reboot. # We call `docker kill lldp` to ensure the container stops as quickly as possible, # then immediately call `systemctl stop lldp` to prevent the service from From b84d67950b8aafbe779a962c7575fbe41f00d77d Mon Sep 17 00:00:00 2001 From: Akhilesh Samineni Date: Fri, 22 Nov 2019 01:03:52 -0800 Subject: [PATCH 8/9] Added command Reference and addressed review comments. Signed-off-by: Akhilesh Samineni --- config/nat.py | 8 +- doc/Command-Reference.md | 360 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 364 insertions(+), 4 deletions(-) diff --git a/config/nat.py b/config/nat.py index b77b0885b3..1c30aa80d4 100644 --- a/config/nat.py +++ b/config/nat.py @@ -14,7 +14,7 @@ def is_valid_ipv4_address(address): ip = ipaddress.IPv4Address(address) if (ip.is_reserved) or (ip.is_multicast) or (ip.is_loopback) or (address in invalid_list): return False - except: + except ipaddress.AddressValueError: return False return True @@ -296,7 +296,7 @@ def add_basic(ctx, global_ip, local_ip, nat_type, twice_nat_id): max_entries = counter_entry['MAX_NAT_ENTRIES'] if int(snat_entries) >= int(max_entries): - click.echo("Max limit 1024 is reached for NAT entries, skipping adding the entry.") + click.echo("Max limit is reached for NAT entries, skipping adding the entry.") entryFound = True if entryFound is False: @@ -376,7 +376,7 @@ def add_tcp(ctx, global_ip, global_port, local_ip, local_port, nat_type, twice_n max_entries = counter_entry['MAX_NAT_ENTRIES'] if int(snat_entries) >= int(max_entries): - click.echo("Max limit 1024 is reached for NAT entries, skipping adding the entry.") + click.echo("Max limit is reached for NAT entries, skipping adding the entry.") entryFound = True if entryFound is False: @@ -456,7 +456,7 @@ def add_udp(ctx, global_ip, global_port, local_ip, local_port, nat_type, twice_n max_entries = counter_entry['MAX_NAT_ENTRIES'] if int(snat_entries) >= int(max_entries): - click.echo("Max limit 1024 is reached for NAT entries, skipping adding the entry.") + click.echo("Max limit is reached for NAT entries, skipping adding the entry.") entryFound = True if entryFound is False: diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index b8e98af88f..f499f85f97 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -56,6 +56,10 @@ * [Mirroring](#mirroring) * [Mirroring Show commands](#mirroring-show-commands) * [Mirroring Config commands](#mirroring-config-commands) +* [NAT](#nat) + * [NAT Show commands](#nat-show-commands) + * [NAT Config commands](#nat-config-commands) + * [NAT Clear commands](#nat-clear-commands) * [NTP](#ntp) * [NTP show commands](#ntp-show-commands) * [NTP config commands](#ntp-config-commands) @@ -243,6 +247,7 @@ This command lists all the possible configuration commands at the top level. load_mgmt_config Reconfigure hostname and mgmt interface based... load_minigraph Reconfigure based on minigraph. mirror_session + nat NAT-related configuration tasks platform Platform-related configuration tasks portchannel qos @@ -291,6 +296,7 @@ This command displays the full list of show commands available in the software; mac Show MAC (FDB) entries mirror_session Show existing everflow sessions mmu Show mmu configuration + nat Show details of the nat ndp Show IPv6 Neighbour table ntp Show NTP information pfc Show details of the priority-flow-control... @@ -372,6 +378,8 @@ This command displays relevant information as the SONiC and Linux kernel version docker-syncd-brcm latest 434240daff6e 362MB docker-orchagent-brcm HEAD.32-21ea29a e4f9c4631025 287MB docker-orchagent-brcm latest e4f9c4631025 287MB + docker-nat HEAD.32-21ea29a 46075edc1c69 305MB + docker-nat latest 46075edc1c69 305MB docker-lldp-sv2 HEAD.32-21ea29a 9681bbfea3ac 275MB docker-lldp-sv2 latest 9681bbfea3ac 275MB docker-dhcp-relay HEAD.32-21ea29a 2db34c7bc6f4 257MB @@ -2844,6 +2852,349 @@ While adding a new session, users need to configure the following fields that ar Go Back To [Beginning of the document](#) or [Beginning of this section](#mirroring) +## NAT + +### NAT Show commands + +**show nat config** + +This command displays the NAT configuration. + +- Usage: + ``` + show nat config [static | pool | bindings | globalvalues | zones] + ``` + +With no optional arguments, the whole NAT configuration is displayed. + +- Example: + ``` + root@sonic:/# show nat config static + + Nat Type IP Protocol Global IP Global L4 Port Local IP Local L4 Port Twice-Nat Id + -------- ----------- ------------ -------------- ------------- ------------- ------------ + dnat all 65.55.45.5 --- 10.0.0.1 --- --- + dnat all 65.55.45.6 --- 10.0.0.2 --- --- + dnat tcp 65.55.45.7 2000 20.0.0.1 4500 1 + snat tcp 20.0.0.2 4000 65.55.45.8 1030 1 + + root@sonic:/# show nat config pool + + Pool Name Global IP Range Global L4 Port Range + ------------ ------------------------- -------------------- + Pool1 65.55.45.5 1024-65535 + Pool2 65.55.45.6-65.55.45.8 --- + Pool3 65.55.45.10-65.55.45.15 500-1000 + + root@sonic:/# show nat config bindings + + Binding Name Pool Name Access-List Nat Type Twice-Nat Id + ------------ ------------ ------------ -------- ------------ + Bind1 Pool1 --- snat --- + Bind2 Pool2 1 snat 1 + Bind3 Pool3 2 snat -- + + root@sonic:/# show nat config globalvalues + + Admin Mode : enabled + Global Timeout : 600 secs + TCP Timeout : 86400 secs + UDP Timeout : 300 secs + + root@sonic:/# show nat config zones + + Port Zone + ---- ---- + Ethernet2 0 + Vlan100 1 + ``` + +**show nat statistics** + +This command displays the NAT translation statistics for each entry. + +- Usage: + ``` + show nat statistics + ``` + +- Example: + ``` + root@sonic:/# show nat statistics + + Protocol Source Destination Packets Bytes + -------- --------- -------------- ------------- ------------- + all 10.0.0.1 --- 802 1009280 + all 10.0.0.2 --- 23 5590 + tcp 20.0.0.1:4500 --- 110 12460 + udp 20.0.0.1:4000 --- 1156 789028 + tcp 20.0.0.1:6000 --- 30 34800 + tcp 20.0.0.1:5000 65.55.42.1:2000 128 110204 + tcp 20.0.0.1:5500 65.55.42.1:2000 8 3806 + ``` + +**show nat translations** + +This command displays the NAT translation entries. + +- Usage: + ``` + show nat translations [count] + ``` +Giving the optional count argument displays only the details about the number of translation entries. +- Example: + ``` + root@sonic:/# show nat translations + + Static NAT Entries ................. 4 + Static NAPT Entries ................. 2 + Dynamic NAT Entries ................. 0 + Dynamic NAPT Entries ................. 4 + Static Twice NAT Entries ................. 0 + Static Twice NAPT Entries ................. 4 + Dynamic Twice NAT Entries ................ 0 + Dynamic Twice NAPT Entries ................ 0 + Total SNAT/SNAPT Entries ................ 9 + Total DNAT/DNAPT Entries ................ 9 + Total Entries ................ 14 + + Protocol Source Destination Translated Source Translated Destination + -------- --------- -------------- ----------------- ---------------------- + all 10.0.0.1 --- 65.55.42.2 --- + all --- 65.55.42.2 --- 10.0.0.1 + all 10.0.0.2 --- 65.55.42.3 --- + all --- 65.55.42.3 --- 10.0.0.2 + tcp 20.0.0.1:4500 --- 65.55.42.1:2000 --- + tcp --- 65.55.42.1:2000 --- 20.0.0.1:4500 + udp 20.0.0.1:4000 --- 65.55.42.1:1030 --- + udp --- 65.55.42.1:1030 --- 20.0.0.1:4000 + tcp 20.0.0.1:6000 --- 65.55.42.1:1024 --- + tcp --- 65.55.42.1:1024 --- 20.0.0.1:6000 + tcp 20.0.0.1:5000 65.55.42.1:2000 65.55.42.1:1025 20.0.0.1:4500 + tcp 20.0.0.1:4500 65.55.42.1:1025 65.55.42.1:2000 20.0.0.1:5000 + tcp 20.0.0.1:5500 65.55.42.1:2000 65.55.42.1:1026 20.0.0.1:4500 + tcp 20.0.0.1:4500 65.55.42.1:1026 65.55.42.1:2000 20.0.0.1:5500 + + root@sonic:/# show nat translations count + + Static NAT Entries ................. 4 + Static NAPT Entries ................. 2 + Dynamic NAT Entries ................. 0 + Dynamic NAPT Entries ................. 4 + Static Twice NAT Entries ................. 0 + Static Twice NAPT Entries ................. 4 + Dynamic Twice NAT Entries ................ 0 + Dynamic Twice NAPT Entries ................ 0 + Total SNAT/SNAPT Entries ................ 9 + Total DNAT/DNAPT Entries ................ 9 + Total Entries ................ 14 + ``` + +### NAT Config commands + +**config nat add static** + +This command is used to add a static NAT or NAPT entry. +When configuring the Static NAT entry, user has to specify the following fields with 'basic' keyword. + +1. Global IP address, +2. Local IP address, +3. NAT type (snat / dnat) to be applied on the Global IP address. Default value is dnat. This is optinoal argument. +4. Twice NAT Id. This is optional argument used in case of twice nat configuration. + +When configuring the Static NAPT entry, user has to specify the following fields. + +1. IP protocol type (tcp / udp) +2. Global IP address + Port +3. Local IP address + Port +4. NAT type (snat / dnat) to be applied on the Global IP address + Port. Default value is dnat. This is optional argument. +5. Twicw NAT Id. This is optional argument used in case of twice nat configuration. + +- Usage: + ``` + config nat add static {{basic (global-ip) (local-ip)} | {{tcp | udp} (global-ip) (global-port) (local-ip) (local-port)}} [-nat_type {snat | dnat}] [-twice_nat_id (value)] + ``` + +To delete a static NAT or NAPT entry, use the command below. Giving the all argument deletes all the configured static NAT and NAPT entries. +``` +config nat remove static {{basic (global-ip) (local-ip)} | {{tcp | udp} (global-ip) (global-port) (local-ip) (local-port)} | all} +``` +- Example: + ``` + root@sonic:/# config nat add static basic 65.55.45.1 12.12.12.14 -nat_type dnat + root@sonic:/# config nat add static tcp 65.55.45.2 100 12.12.12.15 200 -nat_type dnat + + root@sonic:/# show nat translations + + Static NAT Entries ................. 2 + Static NAPT Entries ................. 2 + Dynamic NAT Entries ................. 0 + Dynamic NAPT Entries ................. 0 + Static Twice NAT Entries ................. 0 + Static Twice NAPT Entries ................. 0 + Dynamic Twice NAT Entries ................ 0 + Dynamic Twice NAPT Entries ................ 0 + Total SNAT/SNAPT Entries ................ 2 + Total DNAT/DNAPT Entries ................ 2 + Total Entries ................ 4 + + Protocol Source Destination Translated Source Translated Destination + -------- --------- -------------- ----------------- ---------------------- + all 12.12.12.14 --- 65.55.42.1 --- + all --- 65.55.42.1 --- 12.12.12.14 + tcp 12.12.12.15:200 --- 65.55.42.2:100 --- + tcp --- 65.55.42.2:100 --- 12.12.12.15:200 + ``` + +**config nat add pool** + +This command is used to create a NAT pool used for dynamic Source NAT or NAPT translations. +Pool can be configured in one of the following combinations. + +1. Global IP address range (or) +2. Global IP address + L4 port range (or) +3. Global IP address range + L4 port range. + +- Usage: + ``` + config nat add pool (pool-name) (global-ip-range) (global-port-range) + ``` +To delete a NAT pool, use the command. Pool cannot be removed if it is referenced by a NAT binding. Giving the pools argument removes all the configured pools. +``` +config nat remove {pool (pool-name) | pools} +``` +- Example: + ``` + root@sonic:/# config nat add pool pool1 65.55.45.2-65.55.45.10 + root@sonic:/# config nat add pool pool2 65.55.45.3 100-1024 + + root@sonic:/# show nat config pool + + Pool Name Global IP Range Global Port Range + ----------- ---------------------- ------------------- + pool1 65.55.45.2-65.55.45.10 --- + pool2 65.55.45.3 100-1024 + ``` + +**config nat add binding** + +This command is used to create a NAT binding between a pool and an ACL. The following fields are needed for configuring the binding. + + 1. ACL is an optional argument. If ACL argument is not given, the NAT binding is applicable to match all traffic. + 2. NAT type is an optional argument. Only DNAT type is supoprted for binding. + 3. Twice NAT Id is an optional argument. This Id is used to form a twice nat grouping with the static NAT/NAPT entry configured with the same Id. + +- Usage: + ``` + config nat add binding (binding-name) [(pool-name)] [(acl-name)] [-nat_type {snat | dnat}] [-twice_nat_id (value)] + ``` +To delete a NAT binding, use the command below. Giving the bindings argument removes all the configured bindings. +``` +config nat remove {binding (binding-name) | bindings} +``` +- Example: + ``` + root@sonic:/# config nat add binding bind1 pool1 acl1 + root@sonic:/# config nat add binding bind2 pool2 + + root@sonic:/# show nat config bindings + + Binding Name Pool Name Access-List Nat Type Twice-NAT Id + -------------- ----------- ------------- ---------- -------------- + bind1 pool1 acl1 snat --- + bind2 pool2 snat --- + ``` + +**config nat add interface** + +This command is used to configure NAT zone on an L3 interface. Default value of NAT zone on an L3 interface is 0. Valid range of zone values is 0-3. + +- Usage: + ``` + config nat add interface (interface-name) -nat_zone (value) + ``` +To reset the NAT zone on an interface, use the command below. Giving the interfaces argument resets the NAT zone on all the L3 interfaces to 0. +``` +config nat remove {interface (interface-name) | interfaces} +``` +- Example: + ``` + root@sonic:/# config nat add interface Ethernet28 -nat_zone 1 + + root@sonic:/# show nat config zones + + Port Zone + ---------- ------ + Ethernet0 0 + Ethernet28 1 + Ethernet22 0 + Vlan2091 0 + ``` + +**config nat set** + +This command is used to set the NAT timeout values. Different timeout values can be configured for the NAT entry timeout, NAPT TCP entry timeout, NAPT UDP entry timeout. +Range for Global NAT entry timeout is 300 sec to 432000 sec, default value is 600 sec. +Range for TCP NAT/NAPT entry timeout is 300 sec to 432000 sec, default value is 86400 sec. +Range for UDP NAT/NAPT entry timeout is 120 sec to 600 sec, default value is 300 sec. + +- Usage: + ``` + config nat set {tcp-timeout (value) | timeout (value) | udp-timeout (value)} + ``` +To reset the timeout values to the default values, use the command +``` +config nat reset {tcp-timeout | timeout | udp-timeout} +``` +- Example: + ``` + root@sonic:/# config nat add set tcp-timeout 3600 + + root@sonic:/# show nat config globalvalues + + Admin Mode : enabled + Global Timeout : 600 secs + TCP Timeout : 600 secs + UDP Timeout : 300 secs + ``` + +**config nat feature** + +This command is used to enable or disable the NAT feature. + +- Usage: + ``` + config nat feature {enable | disable} + ``` + +- Example: + ``` + root@sonic:/# config nat feature enable + root@sonic:/# config nat feature disable + ``` + +### NAT Clear commands + +**sonic-clear nat translations** + +This command is used to clear the dynamic NAT and NAPT translation entries. + +- Usage: + ``` + sonic-clear nat translations + ``` + +**sonic-clear nat statistics** + +This command is used to clear the statistics of all the NAT and NAPT entries. + +- Usage: + ``` + sonic-clear nat statistics + ``` + +Go Back To [Beginning of the document](#) or [Beginning of this section](#nat) + ## NTP @@ -3709,6 +4060,14 @@ This command displays the state of all the SONiC processes running inside a dock root 1 0 0 05:26 ? 00:00:12 /usr/bin/python /usr/bin/supervi root 24 1 0 05:26 ? 00:00:00 /usr/sbin/rsyslogd -n + nat docker + --------------------------- + USER PID PPID C STIME TTY TIME CMD + root 1 0 0 05:26 ? 00:00:12 /usr/bin/python /usr/bin/supervisord + root 18 1 0 05:26 ? 00:00:00 /usr/sbin/rsyslogd -n + root 23 1 0 05:26 ? 00:00:01 /usr/bin/natmgrd + root 34 1 0 05:26 ? 00:00:00 /usr/bin/natsyncd + snmp docker --------------------------- UID PID PPID C STIME TTY TIME CMD @@ -4162,6 +4521,7 @@ This command displays the warm_restart state. neighsyncd 0 teamsyncd 1 syncd 0 + natsyncd 0 ``` Go Back To [Beginning of the document](#) or [Beginning of this section](#warm-restart) From 7761091f542d1bd92b1c28a59da68672e5ec0111 Mon Sep 17 00:00:00 2001 From: Akhilesh Samineni Date: Sat, 23 Nov 2019 11:03:42 -0800 Subject: [PATCH 9/9] Addressed review comments. Signed-off-by: Akhilesh Samineni --- scripts/natshow | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/natshow b/scripts/natshow index e02c2f1c15..d0dc753702 100644 --- a/scripts/natshow +++ b/scripts/natshow @@ -171,11 +171,11 @@ class NatShow(object): else: translated_src = translated_src_ip - source_ip = nat["key.src_ip"] - destination_ip = nat["key.dst_ip"] - source_port = nat["key.l4_src_port"] - destination_port = nat["key.l4_dst_port"] - protocol = nat["key.proto"] + source_ip = nat['nat_data']['key']["src_ip"] + destination_ip = nat['nat_data']['key']["dst_ip"] + source_port = nat['nat_data']['key']["l4_src_port"] + destination_port = nat['nat_data']['key']["l4_dst_port"] + protocol = nat['nat_data']['key']["proto"] if (source_ip == "0.0.0.0"): source_ip = "---"