Skip to content

Commit

Permalink
Changes in sonic-buildimage to support the NAT feature (sonic-net#3494)
Browse files Browse the repository at this point in the history
* Changes in sonic-buildimage for the NAT feature
- Docker for NAT
- installing the required tools iptables and conntrack for nat

Signed-off-by: [email protected]

* Add redis-tools dependencies in the docker nat compilation

* Addressed review comments

* add natsyncd to warm-boot finalizer list

* addressed review comments

* using swsscommon.DBConnector instead of swsssdk.SonicV2Connector

* Enable NAT application in docker-sonic-vs
  • Loading branch information
kirankella authored and tiantianlv committed Apr 24, 2020
1 parent 2d5a01e commit 86c5c5c
Show file tree
Hide file tree
Showing 18 changed files with 636 additions and 3 deletions.
3 changes: 2 additions & 1 deletion build_debian.sh
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,8 @@ sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y in
cgroup-tools \
ipmitool \
ndisc6 \
makedumpfile
makedumpfile \
conntrack


if [[ $CONFIGURED_ARCH == amd64 ]]; then
Expand Down
46 changes: 46 additions & 0 deletions dockers/docker-nat/Dockerfile.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{% from "dockers/dockerfile-macros.j2" import install_debian_packages, copy_files %}
FROM docker-config-engine-stretch

ARG docker_container_name
RUN [ -f /etc/rsyslog.conf ] && sed -ri "s/%syslogtag%/$docker_container_name#%syslogtag%/;" /etc/rsyslog.conf

RUN echo

## Make apt-get non-interactive
ENV DEBIAN_FRONTEND=noninteractive

## Install redis-tools dependencies
## TODO: implicitly install dependencies
RUN apt-get update \
&& apt-get install -f -y \
libdbus-1-3 \
libdaemon0 \
libjansson4 \
libpython2.7 \
libatomic1 \
libjemalloc1 \
liblua5.1-0 \
lua-bitop \
lua-cjson \
libelf1 \
libmnl0 \
bridge-utils \
conntrack

{% if docker_nat_debs.strip() -%}
# Copy locally-built Debian package dependencies
{{copy_files ("debs/", docker_nat_debs.split(' '), "/debs/") }}

# Install locally-built Debian packages and implicitly install their dependencies
{{ install_debian_packages(docker_nat_debs.split(' ')) }}
{%- endif %}

COPY ["start.sh", "/usr/bin/"]
COPY ["supervisord.conf", "/etc/supervisor/conf.d/"]
COPY ["restore_nat_entries.py", "/usr/bin/"]

RUN apt-get clean -y; apt-get autoclean -y; apt-get autoremove -y
RUN rm -rf /debs

ENTRYPOINT ["/usr/bin/supervisord"]

5 changes: 5 additions & 0 deletions dockers/docker-nat/base_image_files/natctl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

# -t option needed only for shell, not for commands

docker exec -i nat natctl "$@"
104 changes: 104 additions & 0 deletions dockers/docker-nat/restore_nat_entries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python

""""
Description: restore_nat_entries.py -- restoring nat entries table into kernel during system warm reboot.
The script is started by supervisord in nat docker when the docker is started.
It does not do anything in case neither system nor nat warm restart is enabled.
In case nat warm restart enabled only, it sets the stateDB flag so natsyncd can continue
the reconciation process.
In case system warm reboot is enabled, it will try to restore the nat entries table into kernel
, then it sets the stateDB flag for natsyncd to continue the
reconciliation process.
"""

import sys
import subprocess
from swsscommon import swsscommon
import logging
import logging.handlers
import re
import os

WARM_BOOT_FILE_DIR = '/var/warmboot/nat/'
NAT_WARM_BOOT_FILE = 'nat_entries.dump'
IP_PROTO_TCP = '6'

MATCH_CONNTRACK_ENTRY = '^(\w+)\s+(\d+).*src=([\d.]+)\s+dst=([\d.]+)\s+sport=(\d+)\s+dport=(\d+).*src=([\d.]+)\s+dst=([\d.]+)\s+sport=(\d+)\s+dport=(\d+)'
REDIS_SOCK = "/var/run/redis/redis.sock"

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = logging.handlers.SysLogHandler(address = '/dev/log')
logger.addHandler(handler)

def add_nat_conntrack_entry_in_kernel(ipproto, srcip, dstip, srcport, dstport, natsrcip, natdstip, natsrcport, natdstport):
# pyroute2 doesn't have support for adding conntrack entries via netlink yet. So, invoking the conntrack utility to add the entries.
state = ''
if (ipproto == IP_PROTO_TCP):
state = ' --state ESTABLISHED '
ctcmd = 'conntrack -I -n ' + natdstip + ':' + natdstport + ' -g ' + natsrcip + ':' + natsrcport + \
' --protonum ' + ipproto + state + ' --timeout 600 --src ' + srcip + ' --sport ' + srcport + \
' --dst ' + dstip + ' --dport ' + dstport + ' -u ASSURED'
subprocess.call(ctcmd, shell=True)
logger.info("Restored NAT entry: {}".format(ctcmd))

# Set the statedb "NAT_RESTORE_TABLE|Flags", so natsyncd can start reconciliation
def set_statedb_nat_restore_done():
statedb = swsscommon.DBConnector(swsscommon.STATE_DB, REDIS_SOCK, 0)
tbl = swsscommon.Table(statedb, "NAT_RESTORE_TABLE")
fvs = swsscommon.FieldValuePairs([("restored", "true")])
tbl.set("Flags", fvs)
return

# This function is to restore the kernel nat entries based on the saved nat entries.
def restore_update_kernel_nat_entries(filename):
# Read the entries from nat_entries.dump file and add them to kernel
conntrack_match_pattern = re.compile(r'{}'.format(MATCH_CONNTRACK_ENTRY))
with open(filename, 'r') as fp:
for line in fp:
ctline = conntrack_match_pattern.findall(line)
if not ctline:
continue
cmdargs = list(ctline.pop(0))
proto = cmdargs.pop(0)
if proto not in ('tcp', 'udp'):
continue
add_nat_conntrack_entry_in_kernel(*cmdargs)

def main():
logger.info("restore_nat_entries service is started")

# Use warmstart python binding to check warmstart information
warmstart = swsscommon.WarmStart()
warmstart.initialize("natsyncd", "nat")
warmstart.checkWarmStart("natsyncd", "nat", False)

# if swss or system warm reboot not enabled, don't run
if not warmstart.isWarmStart():
logger.info("restore_nat_entries service is skipped as warm restart not enabled")
return

# NAT restart not system warm reboot, set statedb directly
if not warmstart.isSystemWarmRebootEnabled():
set_statedb_nat_restore_done()
logger.info("restore_nat_entries service is done as system warm reboot not enabled")
return

# Program the nat conntrack entries in the kernel by reading the
# entries from nat_entries.dump
try:
restore_update_kernel_nat_entries(WARM_BOOT_FILE_DIR + NAT_WARM_BOOT_FILE)
except Exception as e:
logger.exception(str(e))
sys.exit(1)

# Remove the dump file after restoration
os.remove(WARM_BOOT_FILE_DIR + NAT_WARM_BOOT_FILE)

# set statedb to signal other processes like natsyncd
set_statedb_nat_restore_done()
logger.info("restore_nat_entries service is done for system warmreboot")
return

if __name__ == '__main__':
main()
15 changes: 15 additions & 0 deletions dockers/docker-nat/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash

rm -f /var/run/rsyslogd.pid
rm -f /var/run/nat/*

mkdir -p /var/warmboot/nat

supervisorctl start rsyslogd

supervisorctl start natmgrd

supervisorctl start natsyncd

supervisorctl start restore_nat_entries

47 changes: 47 additions & 0 deletions dockers/docker-nat/supervisord.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[supervisord]
logfile_maxbytes=1MB
logfile_backups=2
nodaemon=true

[program:start.sh]
command=/usr/bin/start.sh
priority=1
autostart=true
autorestart=false
stdout_logfile=syslog
stderr_logfile=syslog

[program:rsyslogd]
command=/usr/sbin/rsyslogd -n
priority=2
autostart=false
autorestart=false
stdout_logfile=syslog
stderr_logfile=syslog

[program:natmgrd]
command=/usr/bin/natmgrd
priority=3
autostart=false
autorestart=false
stdout_logfile=syslog
stderr_logfile=syslog

[program:natsyncd]
command=/usr/bin/natsyncd
priority=4
autostart=false
autorestart=false
stdout_logfile=syslog
stderr_logfile=syslog

[program:restore_nat_entries]
command=/usr/bin/restore_nat_entries.py
priority=5
autostart=false
autorestart=false
startsecs=0
startretries=0
stdout_logfile=syslog
stderr_logfile=syslog

3 changes: 2 additions & 1 deletion dockers/docker-orchagent/Dockerfile.j2
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ RUN apt-get update && \
tcpdump \
libelf1 \
libmnl0 \
bridge-utils
bridge-utils \
conntrack

{% if ( CONFIGURED_ARCH == "armhf" or CONFIGURED_ARCH == "arm64" ) %}
## Fix for gcc/python not found in arm docker
Expand Down
15 changes: 15 additions & 0 deletions files/build_templates/nat.service.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[Unit]
Description=NAT container
Requires=updategraph.service swss.service
After=updategraph.service swss.service syncd.service
Before=ntp-config.service

[Service]
User={{ sonicadmin_user }}
ExecStartPre=/usr/bin/{{docker_container_name}}.sh start
ExecStart=/usr/bin/{{docker_container_name}}.sh wait
ExecStop=/usr/bin/{{docker_container_name}}.sh stop

[Install]
WantedBy=multi-user.target swss.service

4 changes: 4 additions & 0 deletions files/build_templates/sonic_debian_extension.j2
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ sudo mkdir -p $FILESYSTEM_ROOT_USR_SHARE_SONIC_TEMPLATES/
sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/ifupdown2_*.deb || \
sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f

# Install ipables (and its dependencies via 'apt-get -y install -f')
sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/iptables_*.deb || \
sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f

# Install dependencies for SONiC config engine
sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install \
python-dev \
Expand Down
2 changes: 1 addition & 1 deletion files/image_config/warmboot-finalizer/finalize-warmboot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
VERBOSE=no

# Check components
COMP_LIST="orchagent neighsyncd bgp"
COMP_LIST="orchagent neighsyncd bgp natsyncd"
EXP_STATE="reconciled"

ASSISTANT_SCRIPT="/usr/bin/neighbor_advertiser"
Expand Down
4 changes: 4 additions & 0 deletions platform/vs/docker-sonic-vs/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ supervisorctl start vxlanmgrd

supervisorctl start sflowmgrd

supervisorctl start natmgrd

supervisorctl start natsyncd

# Start arp_update when VLAN exists
VLAN=`sonic-cfggen -d -v 'VLAN.keys() | join(" ") if VLAN'`
if [ "$VLAN" != "" ]; then
Expand Down
16 changes: 16 additions & 0 deletions platform/vs/docker-sonic-vs/supervisord.conf
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,19 @@ autostart=false
autorestart=false
stdout_logfile=syslog
stderr_logfile=syslog

[program:natmgrd]
command=/usr/bin/natmgrd
priority=23
autostart=false
autorestart=false
stdout_logfile=syslog
stderr_logfile=syslog

[program:natsyncd]
command=/usr/bin/natsyncd
priority=24
autostart=false
autorestart=false
stdout_logfile=syslog
stderr_logfile=syslog
30 changes: 30 additions & 0 deletions rules/docker-nat.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# docker image for nat

DOCKER_NAT_STEM = docker-nat
DOCKER_NAT = $(DOCKER_NAT_STEM).gz
DOCKER_NAT_DBG = $(DOCKER_NAT_STEM)-$(DBG_IMAGE_MARK).gz

$(DOCKER_NAT)_PATH = $(DOCKERS_PATH)/$(DOCKER_NAT_STEM)

$(DOCKER_NAT)_DEPENDS += $(SWSS) $(REDIS_TOOLS) $(IPTABLESIP4TC) $(IPTABLESIP6TC) $(IPTABLESIPTC) $(IPXTABLES12) $(IPTABLES)
$(DOCKER_NAT)_DBG_DEPENDS = $($(DOCKER_CONFIG_ENGINE_STRETCH)_DBG_DEPENDS)
$(DOCKER_NAT)_DBG_DEPENDS += $(SWSS_DBG) $(LIBSWSSCOMMON_DBG)
$(DOCKER_NAT)_DBG_IMAGE_PACKAGES = $($(DOCKER_CONFIG_ENGINE_STRETCH)_DBG_IMAGE_PACKAGES)

$(DOCKER_NAT)_LOAD_DOCKERS += $(DOCKER_CONFIG_ENGINE_STRETCH)

SONIC_DOCKER_IMAGES += $(DOCKER_NAT)
SONIC_INSTALL_DOCKER_IMAGES += $(DOCKER_NAT)
SONIC_STRETCH_DOCKERS += $(DOCKER_NAT)

SONIC_DOCKER_DBG_IMAGES += $(DOCKER_NAT_DBG)
SONIC_INSTALL_DOCKER_DBG_IMAGES += $(DOCKER_NAT_DBG)
SONIC_STRETCH_DBG_DOCKERS += $(DOCKER_NAT_DBG)

$(DOCKER_NAT)_CONTAINER_NAME = nat
$(DOCKER_NAT)_RUN_OPT += --net=host --privileged -t
$(DOCKER_NAT)_RUN_OPT += -v /etc/sonic:/etc/sonic:ro
$(DOCKER_NAT)_RUN_OPT += -v /host/warmboot:/var/warmboot

$(DOCKER_NAT)_BASE_IMAGE_FILES += natctl:/usr/bin/natctl

27 changes: 27 additions & 0 deletions rules/iptables.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# iptables package

IPTABLES_VERSION = 1.6.0+snapshot20161117
IPTABLES_VERSION_SUFFIX = 6
IPTABLES_VERSION_FULL = $(IPTABLES_VERSION)-$(IPTABLES_VERSION_SUFFIX)

IPTABLES = iptables_$(IPTABLES_VERSION_FULL)_amd64.deb
$(IPTABLES)_SRC_PATH = $(SRC_PATH)/iptables
SONIC_MAKE_DEBS += $(IPTABLES)
SONIC_STRETCH_DEBS += $(IPTABLES)

IPTABLESIP4TC = libip4tc0_$(IPTABLES_VERSION_FULL)_amd64.deb
$(eval $(call add_derived_package,$(IPTABLES),$(IPTABLESIP4TC)))

IPTABLESIP6TC = libip6tc0_$(IPTABLES_VERSION_FULL)_amd64.deb
$(eval $(call add_derived_package,$(IPTABLES),$(IPTABLESIP6TC)))

IPTABLESIPTC = libiptc0_$(IPTABLES_VERSION_FULL)_amd64.deb
$(eval $(call add_derived_package,$(IPTABLES),$(IPTABLESIPTC)))

IPXTABLES12 = libxtables12_$(IPTABLES_VERSION_FULL)_amd64.deb
$(eval $(call add_derived_package,$(IPTABLES),$(IPXTABLES12)))

# Export these variables so they can be used in a sub-make
export IPTABLES_VERSION
export IPTABLES_VERSION_FULL
export IPTABLES
3 changes: 3 additions & 0 deletions sonic-slave-stretch/Dockerfile.j2
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ RUN apt-get update && apt-get install -y \
libselinux1-dev \
# For kdump-tools
liblzo2-dev \
# For iptables
libnetfilter-conntrack-dev \
libnftnl-dev \
# For SAI3.7
libprotobuf-dev \
# For DHCP Monitor tool
Expand Down
Loading

0 comments on commit 86c5c5c

Please sign in to comment.