Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[caclmgrd] Add support for multi-ASIC platforms #5022

Merged
merged 16 commits into from
Aug 20, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions files/build_templates/sonic_debian_extension.j2
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ if [[ $CONFIGURED_ARCH == amd64 ]]; then
sudo DEBIAN_FRONTEND=noninteractive dpkg --root=$FILESYSTEM_ROOT -i $debs_path/kdump-tools_*.deb || \
sudo LANG=C DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true chroot $FILESYSTEM_ROOT apt-get -q --no-install-suggests --no-install-recommends --force-no install
fi
# Install python-swss-common package and all its dependent packages
jleveque marked this conversation as resolved.
Show resolved Hide resolved
{% if python_swss_debs.strip() -%}
{% for deb in python_swss_debs.strip().split(' ') -%}
sudo dpkg --root=$FILESYSTEM_ROOT -i {{deb}} || sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f
{% endfor %}
{% endif %}

# Install custom-built monit package and SONiC configuration files
sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/monit_*.deb || \
Expand Down
161 changes: 89 additions & 72 deletions files/image_config/caclmgrd/caclmgrd
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ try:
import subprocess
import sys
import syslog
from swsssdk import ConfigDBConnector
import sonic_device_util
jleveque marked this conversation as resolved.
Show resolved Hide resolved
abdosi marked this conversation as resolved.
Show resolved Hide resolved
from swsscommon import swsscommon
from swsssdk import SonicDBConfig, ConfigDBConnector
jleveque marked this conversation as resolved.
Show resolved Hide resolved
except ImportError as err:
raise ImportError("%s - required module not found" % str(err))

Expand Down Expand Up @@ -88,9 +90,17 @@ class ControlPlaneAclManager(object):
}

def __init__(self):
# Open a handle to the Config database
self.config_db = ConfigDBConnector()
self.config_db.connect()
SonicDBConfig.load_sonic_global_db_config()
self.config_db_map = {}
self.iptables_cmd_prefix = {}
jleveque marked this conversation as resolved.
Show resolved Hide resolved
self.config_db_map[''] = ConfigDBConnector(use_unix_socket_path=True, namespace='')
self.config_db_map[''].connect()
self.iptables_cmd_prefix[''] = ""
namespaces = sonic_device_util.get_all_namespaces()
for front_asic_namespaces in namespaces['front_ns']:
jleveque marked this conversation as resolved.
Show resolved Hide resolved
self.config_db_map[front_asic_namespaces] = ConfigDBConnector(use_unix_socket_path=True, namespace=front_asic_namespaces)
self.config_db_map[front_asic_namespaces].connect()
self.iptables_cmd_prefix[front_asic_namespaces] = "ip netns exec " + front_asic_namespaces + " "

def run_commands(self, commands):
"""
Expand Down Expand Up @@ -133,7 +143,7 @@ class ControlPlaneAclManager(object):
tcp_flags_str = tcp_flags_str[:-1]
return tcp_flags_str

def generate_block_ip2me_traffic_iptables_commands(self):
def generate_block_ip2me_traffic_iptables_commands(self, namespace):
INTERFACE_TABLE_NAME_LIST = [
"LOOPBACK_INTERFACE",
"MGMT_INTERFACE",
Expand All @@ -146,7 +156,7 @@ class ControlPlaneAclManager(object):

# Add iptables rules to drop all packets destined for peer-to-peer interface IP addresses
for iface_table_name in INTERFACE_TABLE_NAME_LIST:
iface_table = self.config_db.get_table(iface_table_name)
iface_table = self.config_db_map[namespace].get_table(iface_table_name)
if iface_table:
for key, _ in iface_table.iteritems():
if not _ip_prefix_in_key(key):
Expand All @@ -160,9 +170,9 @@ class ControlPlaneAclManager(object):
ip_addr = next(ip_ntwrk.hosts()) if iface_table_name == "VLAN_INTERFACE" else ip_ntwrk.network_address

if isinstance(ip_ntwrk, ipaddress.IPv4Network):
block_ip2me_cmds.append("iptables -A INPUT -d {}/{} -j DROP".format(ip_addr, ip_ntwrk.max_prefixlen))
block_ip2me_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -A INPUT -d {}/{} -j DROP".format(ip_addr, ip_ntwrk.max_prefixlen)))
elif isinstance(ip_ntwrk, ipaddress.IPv6Network):
block_ip2me_cmds.append("ip6tables -A INPUT -d {}/{} -j DROP".format(ip_addr, ip_ntwrk.max_prefixlen))
block_ip2me_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -d {}/{} -j DROP".format(ip_addr, ip_ntwrk.max_prefixlen)))
else:
log_warning("Unrecognized IP address type on interface '{}': {}".format(iface_name, ip_ntwrk))

Expand All @@ -182,7 +192,7 @@ class ControlPlaneAclManager(object):
else:
return False

def get_acl_rules_and_translate_to_iptables_commands(self):
def get_acl_rules_and_translate_to_iptables_commands(self, namespace):
"""
Retrieves current ACL tables and rules from Config DB, translates
control plane ACLs into a list of iptables commands that can be run
Expand All @@ -197,72 +207,72 @@ class ControlPlaneAclManager(object):
# First, add iptables commands to set default policies to accept all
# traffic. In case we are connected remotely, the connection will not
# drop when we flush the current rules
iptables_cmds.append("iptables -P INPUT ACCEPT")
iptables_cmds.append("iptables -P FORWARD ACCEPT")
iptables_cmds.append("iptables -P OUTPUT ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -P INPUT ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -P FORWARD ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -P OUTPUT ACCEPT")

# Add iptables command to flush the current rules
iptables_cmds.append("iptables -F")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -F")

# Add iptables command to delete all non-default chains
iptables_cmds.append("iptables -X")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -X")

# Add same set of commands for ip6tables
iptables_cmds.append("ip6tables -P INPUT ACCEPT")
iptables_cmds.append("ip6tables -P FORWARD ACCEPT")
iptables_cmds.append("ip6tables -P OUTPUT ACCEPT")
iptables_cmds.append("ip6tables -F")
iptables_cmds.append("ip6tables -X")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -P INPUT ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -P FORWARD ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -P OUTPUT ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -F")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -X")

# Add iptables/ip6tables commands to allow all traffic from localhost
iptables_cmds.append("iptables -A INPUT -s 127.0.0.1 -i lo -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -s ::1 -i lo -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -A INPUT -s 127.0.0.1 -i lo -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -s ::1 -i lo -j ACCEPT")

# Add iptables/ip6tables commands to allow all incoming packets from established
# connections or new connections which are related to established connections
iptables_cmds.append("iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT")

# Add iptables/ip6tables commands to allow bidirectional ICMPv4 ping and traceroute
# TODO: Support processing ICMPv4 service ACL rules, and remove this blanket acceptance
iptables_cmds.append("iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT")
iptables_cmds.append("iptables -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT")
iptables_cmds.append("iptables -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT")
iptables_cmds.append("iptables -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT")

# Add iptables/ip6tables commands to allow bidirectional ICMPv6 ping and traceroute
# TODO: Support processing ICMPv6 service ACL rules, and remove this blanket acceptance
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type destination-unreachable -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type time-exceeded -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -p icmpv6 --icmpv6-type destination-unreachable -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -p icmpv6 --icmpv6-type time-exceeded -j ACCEPT")

# Add iptables/ip6tables commands to allow all incoming Neighbor Discovery Protocol (NDP) NS/NA/RS/RA messages
# TODO: Support processing NDP service ACL rules, and remove this blanket acceptance
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbor-solicitation -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbor-advertisement -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type router-solicitation -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p icmpv6 --icmpv6-type router-advertisement -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbor-solicitation -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbor-advertisement -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -p icmpv6 --icmpv6-type router-solicitation -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -p icmpv6 --icmpv6-type router-advertisement -j ACCEPT")

# Add iptables/ip6tables commands to allow all incoming IPv4 DHCP packets
iptables_cmds.append("iptables -A INPUT -p udp --dport 67:68 -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p udp --dport 67:68 -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -A INPUT -p udp --dport 67:68 --sport 67:68 -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -p udp --dport 67:68 --sport 67:68 -j ACCEPT")

# Add iptables/ip6tables commands to allow all incoming IPv6 DHCP packets
iptables_cmds.append("iptables -A INPUT -p udp --dport 546:547 -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p udp --dport 546:547 -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -A INPUT -p udp --dport 546:547 --sport 546:547 -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -p udp --dport 546:547 --sport 546:547 -j ACCEPT")

# Add iptables/ip6tables commands to allow all incoming BGP traffic
# TODO: Determine BGP ACLs based on configured device sessions, and remove this blanket acceptance
iptables_cmds.append("iptables -A INPUT -p tcp --dport 179 -j ACCEPT")
iptables_cmds.append("iptables -A INPUT -p tcp --sport 179 -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p tcp --dport 179 -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p tcp --sport 179 -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -A INPUT -p tcp --dport 179 -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -A INPUT -p tcp --sport 179 -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -p tcp --dport 179 -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -p tcp --sport 179 -j ACCEPT")


# Get current ACL tables and rules from Config DB
self._tables_db_info = self.config_db.get_table(self.ACL_TABLE)
self._rules_db_info = self.config_db.get_table(self.ACL_RULE)
self._tables_db_info = self.config_db_map[namespace].get_table(self.ACL_TABLE)
self._rules_db_info = self.config_db_map[namespace].get_table(self.ACL_RULE)

num_ctrl_plane_acl_rules = 0

Expand Down Expand Up @@ -363,58 +373,65 @@ class ControlPlaneAclManager(object):
# Append the packet action as the jump target
rule_cmd += " -j {}".format(rule_props["PACKET_ACTION"])

iptables_cmds.append(rule_cmd)
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + rule_cmd)
num_ctrl_plane_acl_rules += 1

# Add iptables commands to block ip2me traffic
iptables_cmds += self.generate_block_ip2me_traffic_iptables_commands()
iptables_cmds += self.generate_block_ip2me_traffic_iptables_commands(namespace)

# Add iptables/ip6tables commands to allow all incoming packets with TTL of 0 or 1
# This allows the device to respond to tools like tcptraceroute
iptables_cmds.append("iptables -A INPUT -m ttl --ttl-lt 2 -j ACCEPT")
iptables_cmds.append("ip6tables -A INPUT -p tcp -m hl --hl-lt 2 -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -A INPUT -m ttl --ttl-lt 2 -j ACCEPT")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -p tcp -m hl --hl-lt 2 -j ACCEPT")

# Finally, if the device has control plane ACLs configured,
# add iptables/ip6tables commands to drop all other incoming packets
if num_ctrl_plane_acl_rules > 0:
iptables_cmds.append("iptables -A INPUT -j DROP")
iptables_cmds.append("iptables -A FORWARD -j DROP")
iptables_cmds.append("ip6tables -A INPUT -j DROP")
iptables_cmds.append("ip6tables -A FORWARD -j DROP")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -A INPUT -j DROP")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "iptables -A FORWARD -j DROP")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A INPUT -j DROP")
iptables_cmds.append(self.iptables_cmd_prefix[namespace] + "ip6tables -A FORWARD -j DROP")

return iptables_cmds

def update_control_plane_acls(self):
def update_control_plane_acls(self, namespace):
"""
Convenience wrapper which retrieves current ACL tables and rules from
Config DB, translates control plane ACLs into a list of iptables
commands and runs them.
"""
iptables_cmds = self.get_acl_rules_and_translate_to_iptables_commands()

iptables_cmds = self.get_acl_rules_and_translate_to_iptables_commands(namespace)
log_info("Issuing the following iptables commands:")
for cmd in iptables_cmds:
log_info(" " + cmd)

self.run_commands(iptables_cmds)

def notification_handler(self, key, data):
log_info("ACL configuration changed. Updating iptables rules for control plane ACLs...")
self.update_control_plane_acls()

def run(self):
# Unconditionally update control plane ACLs once at start
self.update_control_plane_acls()

# Subscribe to notifications when ACL tables or rules change
self.config_db.subscribe(self.ACL_TABLE,
lambda table, key, data: self.notification_handler(key, data))
self.config_db.subscribe(self.ACL_RULE,
lambda table, key, data: self.notification_handler(key, data))

# Indefinitely listen for Config DB notifications
self.config_db.listen()

# Select Time-out for 10 Seconds
SELECT_TIMEOUT_MS = 1000 * 10
swsscommon.SonicDBConfig.initializeGlobalConfig()
sel = swsscommon.Select()
config_db_subscriber_table_map = {}
for namespace in self.config_db_map.keys():
# Unconditionally update control plane ACLs once at start
self.update_control_plane_acls(namespace)
jleveque marked this conversation as resolved.
Show resolved Hide resolved
acl_db_connector = swsscommon.DBConnector("CONFIG_DB", 0, False, namespace)
jleveque marked this conversation as resolved.
Show resolved Hide resolved
subscribe_acl_table = swsscommon.SubscriberStateTable(acl_db_connector, swsscommon.CFG_ACL_TABLE_TABLE_NAME)
subscribe_acl_rule_table = swsscommon.SubscriberStateTable(acl_db_connector, swsscommon.CFG_ACL_RULE_TABLE_NAME)
sel.addSelectable(subscribe_acl_table)
sel.addSelectable(subscribe_acl_rule_table)
config_db_subscriber_table_map[namespace] = []
config_db_subscriber_table_map[namespace].append(subscribe_acl_table)
config_db_subscriber_table_map[namespace].append(subscribe_acl_rule_table)
while True:
(state, c) = sel.select(SELECT_TIMEOUT_MS)
if state != swsscommon.Select.OBJECT:
continue
namespace = c.getDbNamespace()
for table in config_db_subscriber_table_map[namespace]:
table.pop()
self.update_control_plane_acls(namespace)

# ============================= Functions =============================

Expand Down
5 changes: 4 additions & 1 deletion slave.mk
Original file line number Diff line number Diff line change
Expand Up @@ -800,7 +800,8 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \
$(KDUMP_TOOLS) \
$(LIBPAM_TACPLUS) \
$(LIBNSS_TACPLUS) \
$(MONIT)) \
$(MONIT) \
$(PYTHON_SWSSCOMMON)) \
$$(addprefix $(TARGET_PATH)/,$$($$*_DOCKERS)) \
$$(addprefix $(FILES_PATH)/,$$($$*_FILES)) \
$(if $(findstring y,$(ENABLE_ZTP)),$(addprefix $(IMAGE_DISTRO_DEBS_PATH)/,$(SONIC_ZTP))) \
Expand Down Expand Up @@ -845,6 +846,8 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \
export sonic_yang_models_py3_wheel_path="$(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_YANG_MODELS_PY3))"
export sonic_yang_mgmt_py_wheel_path="$(addprefix $(PYTHON_WHEELS_PATH)/,$(SONIC_YANG_MGMT_PY))"
export multi_instance="false"
export python_swss_debs="$(addprefix $(IMAGE_DISTRO_DEBS_PATH)/,$($(LIBSWSSCOMMON)_RDEPENDS))"
export python_swss_debs+=" $(addprefix $(IMAGE_DISTRO_DEBS_PATH)/,$(LIBSWSSCOMMON)) $(addprefix $(IMAGE_DISTRO_DEBS_PATH)/,$(PYTHON_SWSSCOMMON))"

$(foreach docker, $($*_DOCKERS),\
export docker_image="$(docker)"
Expand Down