Skip to content

Commit

Permalink
Add support for IP interface loopback action (#2192)
Browse files Browse the repository at this point in the history
- What I did
Add support for IP interface loopback action.

- How I did it
Add new commands and handilig, Update the commad reference guide to incldue the new show command

- How to verify it
New CLI unittests were added.

Previous command output (if the output of a command-line utility has changed)
None

New command output (if the output of a command-line utility has changed)
root@sonic:~# config interface ip loopback-action Ethernet0 drop
root@sonic:~# show ip interfaces loopback-action
Interface        Action      
------------     ----------  
Ethernet232   drop
Vlan100          forward
  • Loading branch information
liorghub authored Jul 7, 2022
1 parent 1518ca9 commit 3600639
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 11 deletions.
29 changes: 27 additions & 2 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4208,7 +4208,7 @@ def fec(ctx, interface_name, interface_fec, verbose):
@interface.group(cls=clicommon.AbbreviationGroup)
@click.pass_context
def ip(ctx):
"""Add or remove IP address"""
"""Set IP interface attributes"""
pass

#
Expand Down Expand Up @@ -4339,6 +4339,32 @@ def remove(ctx, interface_name, ip_addr):
command = "ip neigh flush dev {} {}".format(interface_name, str(ip_address))
clicommon.run_command(command)

#
# 'loopback-action' subcommand
#

@ip.command()
@click.argument('interface_name', metavar='<interface_name>', required=True)
@click.argument('action', metavar='<action>', required=True)
@click.pass_context
def loopback_action(ctx, interface_name, action):
"""Set IP interface loopback action"""
config_db = ctx.obj['config_db']

if clicommon.get_interface_naming_mode() == "alias":
interface_name = interface_alias_to_name(config_db, interface_name)
if interface_name is None:
ctx.fail('Interface {} is invalid'.format(interface_name))

if not clicommon.is_interface_in_config_db(config_db, interface_name):
ctx.fail('Interface {} is not an IP interface'.format(interface_name))

allowed_actions = ['drop', 'forward']
if action not in allowed_actions:
ctx.fail('Invalid action')

table_name = get_interface_table_name(interface_name)
config_db.mod_entry(table_name, interface_name, {"loopback_action": action})

#
# buffer commands and utilities
Expand Down Expand Up @@ -4860,7 +4886,6 @@ def unbind(ctx, interface_name):
remove_router_interface_ip_address(config_db, interface_name, ipaddress)
config_db.set_entry(table_name, interface_name, None)


#
# 'ipv6' subgroup ('config interface ipv6 ...')
#
Expand Down
43 changes: 43 additions & 0 deletions doc/Command-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -3657,6 +3657,25 @@ This command is used to display the configured MPLS state for the list of config
Ethernet4 enable
```

**show interfaces loopback-action**

This command displays the configured loopback action

- Usage:
```
show ip interfaces loopback-action
```

- Example:
```
root@sonic:~# show ip interfaces loopback-action
Interface Action
------------ ----------
Ethernet232 drop
Vlan100 forward
```


**show interfaces tpid**

This command displays the key fields of the interfaces such as Operational Status, Administrative Status, Alias and TPID.
Expand Down Expand Up @@ -3803,6 +3822,7 @@ This sub-section explains the following list of configuration on the interfaces.
9) advertised-types - to set interface advertised types
10) type - to set interface type
11) mpls - To add or remove MPLS operation for the interface
12) loopback-action - to set action for packet that ingress and gets routed on the same IP interface

From 201904 release onwards, the “config interface” command syntax is changed and the format is as follows:

Expand Down Expand Up @@ -4336,6 +4356,29 @@ MPLS operation for either physical, portchannel, or VLAN interface can be config
admin@sonic:~$ sudo config interface mpls remove Ethernet4
```

**config interface ip loopback-action <interface_name> <action> (Versions >= 202205)**

This command is used for setting the action being taken on packets that ingress and get routed on the same IP interface.
Loopback action can be set on IP interface from type physical, portchannel, VLAN interface and VLAN subinterface.
Loopback action can be drop or forward.

- Usage:
```
config interface ip loopback-action --help
Usage: config interface ip loopback-action [OPTIONS] <interface_name> <action>
Set IP interface loopback action
Options:
-?, -h, --help Show this message and exit.
```

- Example:
```
admin@sonic:~$ config interface ip loopback-action Ethernet0 drop
admin@sonic:~$ config interface ip loopback-action Ethernet0 forward
```
Go Back To [Beginning of the document](#) or [Beginning of this section](#interfaces)

## Interface Naming Mode
Expand Down
48 changes: 41 additions & 7 deletions show/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -933,15 +933,49 @@ def ip():
# Addresses from all scopes are included. Interfaces with no addresses are
# excluded.
#
@ip.command()

@ip.group(invoke_without_command=True)
@multi_asic_util.multi_asic_click_options
def interfaces(namespace, display):
cmd = "sudo ipintutil -a ipv4"
if namespace is not None:
cmd += " -n {}".format(namespace)
@click.pass_context
def interfaces(ctx, namespace, display):
if ctx.invoked_subcommand is None:
cmd = "sudo ipintutil -a ipv4"
if namespace is not None:
cmd += " -n {}".format(namespace)

cmd += " -d {}".format(display)
clicommon.run_command(cmd)
cmd += " -d {}".format(display)
clicommon.run_command(cmd)

#
# 'show ip interfaces loopback-action' command
#

@interfaces.command()
def loopback_action():
"""show ip interfaces loopback-action"""
config_db = ConfigDBConnector()
config_db.connect()
header = ['Interface', 'Action']
body = []

if_tbl = config_db.get_table('INTERFACE')
vlan_if_tbl = config_db.get_table('VLAN_INTERFACE')
po_if_tbl = config_db.get_table('PORTCHANNEL_INTERFACE')
sub_if_tbl = config_db.get_table('VLAN_SUB_INTERFACE')

all_tables = {}
for tbl in [if_tbl, vlan_if_tbl, po_if_tbl, sub_if_tbl]:
all_tables.update(tbl)

if all_tables:
ifs_action = []
ifs = list(all_tables.keys())
for iface in ifs:
if 'loopback_action' in all_tables[iface]:
action = all_tables[iface]['loopback_action']
ifs_action.append([iface, action])
body = natsorted(ifs_action)
click.echo(tabulate(body, header))

#
# 'route' subcommand ("show ip route")
Expand Down
139 changes: 139 additions & 0 deletions tests/loopback_action_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import os
from click.testing import CliRunner
import config.main as config
import show.main as show
from utilities_common.db import Db

show_ip_interfaces_loopback_action_output="""\
Interface Action
--------------- --------
Eth32.10 drop
Ethernet0 forward
PortChannel0001 drop
Vlan3000 forward
"""

class TestLoopbackAction(object):
@classmethod
def setup_class(cls):
print("\nSETUP")
os.environ['UTILITIES_UNIT_TESTING'] = "1"

def test_config_loopback_action_on_physical_interface(self):
runner = CliRunner()
db = Db()
obj = {'config_db':db.cfgdb}
action = 'drop'
iface = 'Ethernet0'

result = runner.invoke(config.config.commands['interface'].commands["ip"].commands['loopback-action'], [iface, action], obj=obj)

table = db.cfgdb.get_table('INTERFACE')
assert(table[iface]['loopback_action'] == action)

print(result.exit_code, result.output)
assert result.exit_code == 0

def test_config_loopback_action_on_physical_interface_alias(self):
runner = CliRunner()
db = Db()
obj = {'config_db':db.cfgdb}
action = 'forward'
iface = 'Ethernet0'
iface_alias = 'etp1'

os.environ['SONIC_CLI_IFACE_MODE'] = "alias"
result = runner.invoke(config.config.commands['interface'].commands["ip"].commands['loopback-action'], [iface_alias, action], obj=obj)
os.environ['SONIC_CLI_IFACE_MODE'] = "default"

table = db.cfgdb.get_table('INTERFACE')
assert(table[iface]['loopback_action'] == action)

print(result.exit_code, result.output)
assert result.exit_code == 0

def test_config_loopback_action_on_port_channel_interface(self):
runner = CliRunner()
db = Db()
obj = {'config_db':db.cfgdb}
action = 'forward'
iface = 'PortChannel0002'

result = runner.invoke(config.config.commands['interface'].commands["ip"].commands['loopback-action'], [iface, action], obj=obj)

table = db.cfgdb.get_table('PORTCHANNEL_INTERFACE')
assert(table[iface]['loopback_action'] == action)

print(result.exit_code, result.output)
assert result.exit_code == 0

def test_config_loopback_action_on_vlan_interface(self):
runner = CliRunner()
db = Db()
obj = {'config_db':db.cfgdb}
action = 'drop'
iface = 'Vlan1000'

result = runner.invoke(config.config.commands['interface'].commands["ip"].commands['loopback-action'], [iface, action], obj=obj)

table = db.cfgdb.get_table('VLAN_INTERFACE')
assert(table[iface]['loopback_action'] == action)

print(result.exit_code, result.output)
assert result.exit_code == 0

def test_config_loopback_action_on_subinterface(self):
runner = CliRunner()
db = Db()
obj = {'config_db':db.cfgdb}
action = 'forward'
iface = 'Ethernet0.10'

result = runner.invoke(config.config.commands['interface'].commands["ip"].commands['loopback-action'], [iface, action], obj=obj)

table = db.cfgdb.get_table('VLAN_SUB_INTERFACE')
assert(table[iface]['loopback_action'] == action)

print(result.exit_code, result.output)
assert result.exit_code == 0

def test_show_ip_interfaces_loopback_action(self):
runner = CliRunner()
result = runner.invoke(show.cli.commands["ip"].commands["interfaces"].commands["loopback-action"], [])

print(result.exit_code, result.output)
assert result.exit_code == 0
assert result.output == show_ip_interfaces_loopback_action_output

def test_config_loopback_action_on_non_ip_interface(self):
runner = CliRunner()
db = Db()
obj = {'config_db':db.cfgdb}
action = 'forward'
iface = 'Ethernet0.11'
ERROR_MSG = "Error: Interface {} is not an IP interface".format(iface)

result = runner.invoke(config.config.commands['interface'].commands["ip"].commands['loopback-action'], [iface, action], obj=obj)

print(result.exit_code, result.output)
assert result.exit_code != 0
assert ERROR_MSG in result.output

def test_config_loopback_action_invalid_action(self):
runner = CliRunner()
db = Db()
obj = {'config_db':db.cfgdb}
action = 'xforwardx'
iface = 'Ethernet0'
ERROR_MSG = "Error: Invalid action"

result = runner.invoke(config.config.commands['interface'].commands["ip"].commands['loopback-action'], [iface, action], obj=obj)

print(result.exit_code, result.output)
assert result.exit_code != 0
assert ERROR_MSG in result.output

@classmethod
def teardown_class(cls):
print("\nTEARDOWN")
os.environ['UTILITIES_UNIT_TESTING'] = "0"
10 changes: 8 additions & 2 deletions tests/mock_tables/config_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@
},
"VLAN_SUB_INTERFACE|Eth32.10": {
"admin_status": "up",
"loopback_action": "drop",
"vlan": "100"
},
"VLAN_SUB_INTERFACE|Eth32.10|32.10.11.12/24": {
Expand Down Expand Up @@ -562,6 +563,9 @@
"VLAN_INTERFACE|Vlan2000": {
"proxy_arp": "enabled"
},
"VLAN_INTERFACE|Vlan3000": {
"loopback_action": "forward"
},
"VLAN_INTERFACE|Vlan1000|192.168.0.1/21": {
"NULL": "NULL"
},
Expand Down Expand Up @@ -646,7 +650,8 @@
"NULL": "NULL"
},
"PORTCHANNEL_INTERFACE|PortChannel0001": {
"ipv6_use_link_local_only": "disable"
"ipv6_use_link_local_only": "disable",
"loopback_action": "drop"
},
"PORTCHANNEL_INTERFACE|PortChannel0002": {
"NULL": "NULL"
Expand Down Expand Up @@ -682,7 +687,8 @@
"NULL": "NULL"
},
"INTERFACE|Ethernet0": {
"ipv6_use_link_local_only": "disable"
"ipv6_use_link_local_only": "disable",
"loopback_action": "forward"
},
"INTERFACE|Ethernet0|14.14.0.1/24": {
"NULL": "NULL"
Expand Down
1 change: 1 addition & 0 deletions utilities_common/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,7 @@ def is_interface_in_config_db(config_db, interface_name):
if (not interface_name in config_db.get_keys('VLAN_INTERFACE') and
not interface_name in config_db.get_keys('INTERFACE') and
not interface_name in config_db.get_keys('PORTCHANNEL_INTERFACE') and
not interface_name in config_db.get_keys('VLAN_SUB_INTERFACE') and
not interface_name == 'null'):
return False

Expand Down

0 comments on commit 3600639

Please sign in to comment.