Skip to content

Commit

Permalink
[Multi-Asic] Forward SNMP requests received on front panel interface …
Browse files Browse the repository at this point in the history
…to SNMP agent in host. (#5420)

* [Multi-Asic] Forward SNMP requests destined to loopback IP, and coming in through the front panel interface
             present in the network namespace, to SNMP agent running in the linux host.

* Updates based on comments

* Further updates in docker_image_ctl.j2 and caclmgrd

* Change the variable for net config file.

* Updated the comments in the code.

* No need to clean up the exising NAT rules if present, which could be created by some other process.

* Delete our rule first and add it back, to take care of caclmgrd restart.
Another benefit is that we delete only our rules, rather than earlier approach of "iptables -F" which cleans up all rules.

* Keeping the original logic to clean the NAT entries, to revist when NAT feature added in namespace.

* Missing updates to log_info call.
  • Loading branch information
judyjoseph authored and abdosi committed Sep 28, 2020
1 parent b70c6f7 commit cff716f
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 0 deletions.
4 changes: 4 additions & 0 deletions files/build_templates/docker_image_ctl.j2
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ function postStartAction()
{
{%- if docker_container_name == "database" %}
if [ "$DEV" ]; then
# Enable the forwarding on eth0 interface in namespace.
SYSCTL_NET_CONFIG="/etc/sysctl.d/sysctl-net.conf"
docker exec -i database$DEV sed -i -e "s/^net.ipv4.conf.eth0.forwarding=0/net.ipv4.conf.eth0.forwarding=1/;
s/^net.ipv6.conf.eth0.forwarding=0/net.ipv6.conf.eth0.forwarding=1/" $SYSCTL_NET_CONFIG
docker exec -i database$DEV sysctl --system -e
link_namespace $DEV
fi
Expand Down
58 changes: 58 additions & 0 deletions files/image_config/caclmgrd/caclmgrd
Original file line number Diff line number Diff line change
Expand Up @@ -81,26 +81,37 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
self.config_db_map[''].connect()
self.iptables_cmd_ns_prefix[''] = ""
self.namespace_mgmt_ip = self.get_namespace_mgmt_ip(self.iptables_cmd_ns_prefix[''], '')
self.namespace_mgmt_ipv6 = self.get_namespace_mgmt_ipv6(self.iptables_cmd_ns_prefix[''], '')
self.namespace_docker_mgmt_ip = {}
self.namespace_docker_mgmt_ipv6 = {}
namespaces = device_info.get_all_namespaces()
for front_asic_namespace in namespaces['front_ns']:
self.config_db_map[front_asic_namespace] = ConfigDBConnector(use_unix_socket_path=True, namespace=front_asic_namespace)
self.config_db_map[front_asic_namespace].connect()
self.iptables_cmd_ns_prefix[front_asic_namespace] = "ip netns exec " + front_asic_namespace + " "
self.namespace_docker_mgmt_ip[front_asic_namespace] = self.get_namespace_mgmt_ip(self.iptables_cmd_ns_prefix[front_asic_namespace],
front_asic_namespace)
self.namespace_docker_mgmt_ipv6[front_asic_namespace] = self.get_namespace_mgmt_ipv6(self.iptables_cmd_ns_prefix[front_asic_namespace],
front_asic_namespace)

for back_asic_namespace in namespaces['back_ns']:
self.iptables_cmd_ns_prefix[back_asic_namespace] = "ip netns exec " + back_asic_namespace + " "
self.namespace_docker_mgmt_ip[back_asic_namespace] = self.get_namespace_mgmt_ip(self.iptables_cmd_ns_prefix[back_asic_namespace],
back_asic_namespace)
self.namespace_docker_mgmt_ipv6[back_asic_namespace] = self.get_namespace_mgmt_ipv6(self.iptables_cmd_ns_prefix[back_asic_namespace],
back_asic_namespace)

def get_namespace_mgmt_ip(self, iptable_ns_cmd_prefix, namespace):
ip_address_get_command = iptable_ns_cmd_prefix + "ip -4 -o addr show " + ("eth0" if namespace else "docker0") +\
" | awk '{print $4}' | cut -d'/' -f1 | head -1"

return self.run_commands([ip_address_get_command])

def get_namespace_mgmt_ipv6(self, iptable_ns_cmd_prefix, namespace):
ipv6_address_get_command = iptable_ns_cmd_prefix + "ip -6 -o addr show scope global " + ("eth0" if namespace else "docker0") +\
" | awk '{print $4}' | cut -d'/' -f1 | head -1"
return self.run_commands([ipv6_address_get_command])

def run_commands(self, commands):
"""
Given a list of shell commands, run them in order
Expand Down Expand Up @@ -202,6 +213,39 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
(docker_mgmt_ip, self.namespace_mgmt_ip))
return allow_internal_docker_ip_cmds

def generate_fwd_snmp_traffic_from_namespace_to_host_commands(self, namespace):
"""
The below SNAT and DNAT rules are added in asic namespace in multi-ASIC platforms. It helps to forward the SNMP request coming
in through the front panel interfaces created/present in the asic namespace to the SNMP Agent running in SNMP container in
linux host network namespace. The external IP addresses are NATed to the internal docker IP addresses for the SNMP Agent to respond.
"""
fwd_snmp_traffic_from_namespace_to_host_cmds = []

if namespace:
# IPv4 rules
fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -t nat -X")
fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "iptables -t nat -F")

fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
"iptables -t nat -A PREROUTING -p udp --dport {} -j DNAT --to-destination {}".format
(self.ACL_SERVICES['SNMP']['dst_ports'][0], self.namespace_mgmt_ip))
fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
"iptables -t nat -A POSTROUTING -p udp --dport {} -j SNAT --to-source {}".format
(self.ACL_SERVICES['SNMP']['dst_ports'][0], self.namespace_docker_mgmt_ip[namespace]))

# IPv6 rules
fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -t nat -X")
fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] + "ip6tables -t nat -F")

fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
"ip6tables -t nat -A PREROUTING -p udp --dport {} -j DNAT --to-destination {}".format
(self.ACL_SERVICES['SNMP']['dst_ports'][0], self.namespace_mgmt_ipv6))
fwd_snmp_traffic_from_namespace_to_host_cmds.append(self.iptables_cmd_ns_prefix[namespace] +
"ip6tables -t nat -A POSTROUTING -p udp --dport {} -j SNAT --to-source {}".format
(self.ACL_SERVICES['SNMP']['dst_ports'][0], self.namespace_docker_mgmt_ipv6[namespace]))

return fwd_snmp_traffic_from_namespace_to_host_cmds

def is_rule_ipv4(self, rule_props):
if (("SRC_IP" in rule_props and rule_props["SRC_IP"]) or
("DST_IP" in rule_props and rule_props["DST_IP"])):
Expand Down Expand Up @@ -429,6 +473,19 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):

self.run_commands(iptables_cmds)

def update_control_plane_nat_acls(self, namespace):
"""
Convenience wrapper which programs the NAT rules for allowing the
snmp traffic coming on the front panel interface
"""
# Add iptables commands to allow front panel snmp traffic
iptables_cmds = self.generate_fwd_snmp_traffic_from_namespace_to_host_commands(namespace)
self.log_info("Issuing the following iptables commands:")
for cmd in iptables_cmds:
self.log_info(" " + cmd)

self.run_commands(iptables_cmds)

def run(self):
# Select Time-out for 10 Seconds
SELECT_TIMEOUT_MS = 1000 * 10
Expand All @@ -453,6 +510,7 @@ class ControlPlaneAclManager(daemon_base.DaemonBase):
for namespace in self.config_db_map.keys():
# Unconditionally update control plane ACLs once at start on given namespace
self.update_control_plane_acls(namespace)
self.update_control_plane_nat_acls(namespace)
# Connect to Config DB of given namespace
acl_db_connector = swsscommon.DBConnector("CONFIG_DB", 0, False, namespace)
# Subscribe to notifications when ACL tables changes
Expand Down

0 comments on commit cff716f

Please sign in to comment.