[lxc-devel] [lxc/master] hooks: add dhclient hooks

3XX0 on Github lxc-bot at linuxcontainers.org
Tue Nov 21 01:11:38 UTC 2017


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 1443 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20171121/d8bbf838/attachment.bin>
-------------- next part --------------
From 27234deb3a87982b84381319595570f43f0b12f6 Mon Sep 17 00:00:00 2001
From: Jonathan Calmels <jcalmels at nvidia.com>
Date: Wed, 8 Nov 2017 05:58:01 -0500
Subject: [PATCH] hooks: add dhclient hooks

Add new hooks leveraging dhclient from the host to automatically
configure the container interfaces. This is especially useful for
application containers which rely on an IPAM driver for network
configuration (e.g. Docker).

Signed-off-by: Jonathan Calmels <jcalmels at nvidia.com>
---
 .gitignore              |   2 +
 configure.ac            |   2 +
 hooks/Makefile.am       |   3 +
 hooks/dhclient-script   | 388 ++++++++++++++++++++++++++++++++++++++++++++++++
 hooks/dhclient-start.in |  13 ++
 hooks/dhclient-stop.in  |  15 ++
 6 files changed, 423 insertions(+)
 create mode 100644 hooks/dhclient-script
 create mode 100755 hooks/dhclient-start.in
 create mode 100755 hooks/dhclient-stop.in

diff --git a/.gitignore b/.gitignore
index 892489cc9..8385ef793 100644
--- a/.gitignore
+++ b/.gitignore
@@ -136,6 +136,8 @@ doc/manpage.refs
 doc/api/html/*
 
 hooks/unmount-namespace
+hooks/dhclient-start
+hooks/dhclient-stop
 
 m4/
 
diff --git a/configure.ac b/configure.ac
index 63df7466c..eb6fc39c5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -889,6 +889,8 @@ AC_CONFIG_FILES([
 	doc/ko/see_also.sgml
 
 	hooks/Makefile
+	hooks/dhclient-start
+	hooks/dhclient-stop
 
 	templates/Makefile
 	templates/lxc-alpine
diff --git a/hooks/Makefile.am b/hooks/Makefile.am
index 23b85c8f3..b8b8f532d 100644
--- a/hooks/Makefile.am
+++ b/hooks/Makefile.am
@@ -5,6 +5,9 @@ hooks_SCRIPTS = \
 	clonehostname \
 	mountecryptfsroot \
 	ubuntu-cloud-prep \
+	dhclient-script \
+	dhclient-start \
+	dhclient-stop \
 	squid-deb-proxy-client
 
 binhooks_PROGRAMS = \
diff --git a/hooks/dhclient-script b/hooks/dhclient-script
new file mode 100644
index 000000000..171117fd2
--- /dev/null
+++ b/hooks/dhclient-script
@@ -0,0 +1,388 @@
+#!/bin/bash
+# dhclient-script for Linux. Dan Halbert, March, 1997.
+# Updated for Linux 2.[12] by Brian J. Murrell, January 1999.
+# No guarantees about this. I'm a novice at the details of Linux
+# networking.
+
+# Notes:
+
+# 0. This script is based on the netbsd script supplied with dhcp-970306.
+
+# 1. ifconfig down apparently deletes all relevant routes and flushes
+# the arp cache, so this doesn't need to be done explicitly.
+
+# 2. The alias address handling here has not been tested AT ALL.
+# I'm just going by the doc of modern Linux ip aliasing, which uses
+# notations like eth0:0, eth0:1, for each alias.
+
+# 3. I have to calculate the network address, and calculate the broadcast
+# address if it is not supplied. This might be much more easily done
+# by the dhclient C code, and passed on.
+
+# 4. TIMEOUT not tested. ping has a flag I don't know, and I'm suspicious
+# of the $1 in its args.
+
+# 5. Script refresh in 2017. The aliasing code was too convoluted and needs
+# to go away. Migrated DHCPv4 script to ip command from iproute2 suite.
+# This is based on Debian script with some tweaks. ifconfig is no longer
+# used. Everything is done using ip tool from ip-route2.
+
+# 6. LXC Fork
+# Remove hooks and dependency on ifconfig.
+# Resolve every paths against the $ROOTFS environment variable.
+
+# 'ip' just looks too weird. Also, we now have unit-tests! Those unit-tests
+# overwirte this line to use a fake ip-echo tool. It's also convenient
+# if your system holds ip tool in a non-standard location.
+ip=/sbin/ip
+
+# update /etc/resolv.conf based on received values
+# This updated version mostly follows Debian script by Andrew Pollock et al.
+make_resolv_conf() {
+    local resolv_conf="${ROOTFS}/etc/resolv.conf"
+    local new_resolv_conf="${ROOTFS}/etc/resolv.conf.dhclient-new"
+
+    # DHCPv4
+    if [ -n "$new_domain_search" ] || [ -n "$new_domain_name" ] ||
+       [ -n "$new_domain_name_servers" ]; then
+        rm -f $new_resolv_conf
+
+        if [ -n "$new_domain_name" ]; then
+            echo domain ${new_domain_name%% *} >>$new_resolv_conf
+        fi
+
+        if [ -n "$new_domain_search" ]; then
+            if [ -n "$new_domain_name" ]; then
+                domain_in_search_list=""
+                for domain in $new_domain_search; do
+                    if [ "$domain" = "${new_domain_name}" ] ||
+                       [ "$domain" = "${new_domain_name}." ]; then
+                        domain_in_search_list="Yes"
+                    fi
+                done
+                if [ -z "$domain_in_search_list" ]; then
+                    new_domain_search="$new_domain_name $new_domain_search"
+                fi
+            fi
+            echo "search ${new_domain_search}" >> $new_resolv_conf
+        elif [ -n "$new_domain_name" ]; then
+            echo "search ${new_domain_name}" >> $new_resolv_conf
+        fi
+
+        if [ -n "$new_domain_name_servers" ]; then
+            for nameserver in $new_domain_name_servers; do
+                echo nameserver $nameserver >>$new_resolv_conf
+            done
+        else # keep 'old' nameservers
+            sed -n /^\w*[Nn][Aa][Mm][Ee][Ss][Ee][Rr][Vv][Ee][Rr]/p $resolv_conf >>$new_resolv_conf
+        fi
+
+	if [ -f "$resolv_conf" ]; then
+	    chown --reference=$resolv_conf $new_resolv_conf
+	    chmod --reference=$resolv_conf $new_resolv_conf
+	fi
+        mv -f $new_resolv_conf $resolv_conf
+    # DHCPv6
+    elif [ -n "$new_dhcp6_domain_search" ] || [ -n "$new_dhcp6_name_servers" ]; then
+        rm -f $new_resolv_conf
+
+        if [ -n "$new_dhcp6_domain_search" ]; then
+            echo "search ${new_dhcp6_domain_search}" >> $new_resolv_conf
+        fi
+
+        if [ -n "$new_dhcp6_name_servers" ]; then
+            for nameserver in $new_dhcp6_name_servers; do
+                # append %interface to link-local-address nameservers
+                if [ "${nameserver##fe80::}" != "$nameserver" ] ||
+                   [ "${nameserver##FE80::}" != "$nameserver" ]; then
+                    nameserver="${nameserver}%${interface}"
+                fi
+                echo nameserver $nameserver >>$new_resolv_conf
+            done
+        else # keep 'old' nameservers
+            sed -n /^\w*[Nn][Aa][Mm][Ee][Ss][Ee][Rr][Vv][Ee][Rr]/p $resolv_conf >>$new_resolv_conf
+        fi
+
+	if [ -f "$resolv_conf" ]; then
+            chown --reference=$resolv_conf $new_resolv_conf
+            chmod --reference=$resolv_conf $new_resolv_conf
+	fi
+        mv -f $new_resolv_conf $resolv_conf
+    fi
+}
+
+# set host name
+set_hostname() {
+    local current_hostname
+
+    if [ -n "$new_host_name" ]; then
+        current_hostname=$(hostname)
+
+        # current host name is empty, '(none)' or 'localhost' or differs from new one from DHCP
+        if [ -z "$current_hostname" ] ||
+           [ "$current_hostname" = '(none)' ] ||
+           [ "$current_hostname" = 'localhost' ] ||
+           [ "$current_hostname" = "$old_host_name" ]; then
+           if [ "$new_host_name" != "$old_host_name" ]; then
+               hostname "$new_host_name"
+           fi
+        fi
+    fi
+}
+
+# Execute the operation
+case "$reason" in
+
+    ### DHCPv4 Handlers
+
+    MEDIUM|ARPCHECK|ARPSEND)
+        # Do nothing
+        ;;
+    PREINIT)
+        # The DHCP client is requesting that an interface be
+        # configured as required in order to send packets prior to
+        # receiving an actual address. - dhclient-script(8)
+
+        # ensure interface is up
+        ${ip} link set dev ${interface} up
+
+        if [ -n "$alias_ip_address" ]; then
+            # flush alias IP from interface
+            ${ip} -4 addr flush dev ${interface} label ${interface}:0
+        fi
+
+        ;;
+
+    BOUND|RENEW|REBIND|REBOOT)
+        set_hostname
+
+        if [ -n "$old_ip_address" ] && [ -n "$alias_ip_address" ] &&
+           [ "$alias_ip_address" != "$old_ip_address" ]; then
+            # alias IP may have changed => flush it
+            ${ip} -4 addr flush dev ${interface} label ${interface}:0
+        fi
+
+        if [ -n "$old_ip_address" ] &&
+           [ "$old_ip_address" != "$new_ip_address" ]; then
+            # leased IP has changed => flush it
+            ${ip} -4 addr flush dev ${interface} label ${interface}
+        fi
+
+        if [ -z "$old_ip_address" ] ||
+           [ "$old_ip_address" != "$new_ip_address" ] ||
+           [ "$reason" = "BOUND" ] || [ "$reason" = "REBOOT" ]; then
+            # new IP has been leased or leased IP changed => set it
+            ${ip} -4 addr add ${new_ip_address}${new_subnet_mask:+/$new_subnet_mask} \
+                ${new_broadcast_address:+broadcast $new_broadcast_address} \
+                dev ${interface} label ${interface}
+
+            if [ -n "$new_interface_mtu" ]; then
+                # set MTU
+                ${ip} link set dev ${interface} mtu ${new_interface_mtu}
+            fi
+
+	    # if we have $new_rfc3442_classless_static_routes then we have to
+	    # ignore $new_routers entirely
+	    if [ ! "$new_rfc3442_classless_static_routes" ]; then
+		    # set if_metric if IF_METRIC is set or there's more than one router
+		    if_metric="$IF_METRIC"
+		    if [ "${new_routers%% *}" != "${new_routers}" ]; then
+			if_metric=${if_metric:-1}
+		    fi
+
+		    for router in $new_routers; do
+			if [ "$new_subnet_mask" = "255.255.255.255" ]; then
+			    # point-to-point connection => set explicit route
+			    ${ip} -4 route add ${router} dev $interface >/dev/null 2>&1
+			fi
+
+			# set default route
+			${ip} -4 route add default via ${router} dev ${interface} \
+			    ${if_metric:+metric $if_metric} >/dev/null 2>&1
+
+			if [ -n "$if_metric" ]; then
+			    if_metric=$((if_metric+1))
+			fi
+		    done
+	    fi
+        fi
+
+        if [ -n "$alias_ip_address" ] &&
+           [ "$new_ip_address" != "$alias_ip_address" ]; then
+            # separate alias IP given, which may have changed
+            # => flush it, set it & add host route to it
+            ${ip} -4 addr flush dev ${interface} label ${interface}:0
+            ${ip} -4 addr add ${alias_ip_address}${alias_subnet_mask:+/$alias_subnet_mask} \
+                dev ${interface} label ${interface}:0
+            ${ip} -4 route add ${alias_ip_address} dev ${interface} >/dev/null 2>&1
+        fi
+
+        # update /etc/resolv.conf
+        make_resolv_conf
+
+        ;;
+
+    EXPIRE|FAIL|RELEASE|STOP)
+        if [ -n "$alias_ip_address" ]; then
+            # flush alias IP
+            ${ip} -4 addr flush dev ${interface} label ${interface}:0
+        fi
+
+        if [ -n "$old_ip_address" ]; then
+            # flush leased IP
+            ${ip} -4 addr flush dev ${interface} label ${interface}
+        fi
+
+        if [ -n "$alias_ip_address" ]; then
+            # alias IP given => set it & add host route to it
+            ${ip} -4 addr add ${alias_ip_address}${alias_subnet_mask:+/$alias_subnet_mask} \
+                dev ${interface} label ${interface}:0
+            ${ip} -4 route add ${alias_ip_address} dev ${interface} >/dev/null 2>&1
+        fi
+
+        ;;
+
+    TIMEOUT)
+        if [ -n "$alias_ip_address" ]; then
+            # flush alias IP
+            ${ip} -4 addr flush dev ${interface} label ${interface}:0
+        fi
+
+        # set IP from recorded lease
+        ${ip} -4 addr add ${new_ip_address}${new_subnet_mask:+/$new_subnet_mask} \
+            ${new_broadcast_address:+broadcast $new_broadcast_address} \
+            dev ${interface} label ${interface}
+
+        if [ -n "$new_interface_mtu" ]; then
+            # set MTU
+            ${ip} link set dev ${interface} mtu ${new_interface_mtu}
+        fi
+
+        # if there is no router recorded in the lease or the 1st router answers pings
+        if [ -z "$new_routers" ] || ping -q -c 1 "${new_routers%% *}"; then
+	    # if we have $new_rfc3442_classless_static_routes then we have to
+	    # ignore $new_routers entirely
+	    if [ ! "$new_rfc3442_classless_static_routes" ]; then
+		    if [ -n "$alias_ip_address" ] &&
+		       [ "$new_ip_address" != "$alias_ip_address" ]; then
+			# separate alias IP given => set up the alias IP & add host route to it
+			${ip} -4 addr add \
+                              ${alias_ip_address}${alias_subnet_mask:+/$alias_subnet_mask} \
+			      dev ${interface} label ${interface}:0
+			${ip} -4 route add ${alias_ip_address} dev ${interface} >/dev/null 2>&1
+		    fi
+
+		    # set if_metric if IF_METRIC is set or there's more than one router
+		    if_metric="$IF_METRIC"
+		    if [ "${new_routers%% *}" != "${new_routers}" ]; then
+			if_metric=${if_metric:-1}
+		    fi
+
+		    # set default route
+		    for router in $new_routers; do
+			${ip} -4 route add default via ${router} dev ${interface} \
+			    ${if_metric:+metric $if_metric} >/dev/null 2>&1
+
+			if [ -n "$if_metric" ]; then
+			    if_metric=$((if_metric+1))
+			fi
+		    done
+	    fi
+
+            # update /etc/resolv.conf
+            make_resolv_conf
+        else
+            # flush all IPs from interface
+            ${ip} -4 addr flush dev ${interface}
+            exit 2
+        fi
+
+        ;;
+
+    ### DHCPv6 Handlers
+    # TODO handle prefix change: ?based on ${old_ip6_prefix} and ${new_ip6_prefix}?
+
+    PREINIT6)
+        # ensure interface is up
+        ${ip} link set ${interface} up
+
+        # We need to give the kernel some time to active interface
+        interface_up_wait_time=5
+        for i in $(seq 0 ${interface_up_wait_time})
+        do
+            ${ip} addr show ${interface} | grep NO-CARRIER >/dev/null 2>&1
+            if [ $? -ne 0 ]; then
+                break;
+            fi
+            sleep 1
+        done
+
+        # flush any stale global permanent IPs from interface
+        ${ip} -6 addr flush dev ${interface} scope global permanent
+
+        # Wait for duplicate address detection for this interface if the
+        # --dad-wait-time parameter has been specified and is greater than
+        # zero.
+        if [ ${dad_wait_time} -gt 0 ]; then
+            # Check if any IPv6 address on this interface is marked as
+            # tentative.
+            ${ip} addr show ${interface} | grep inet6 | grep tentative \
+                &> /dev/null
+            if [ $? -eq 0 ]; then
+                # Wait for duplicate address detection to complete or for
+                # the timeout specified as --dad-wait-time.
+                for i in $(seq 0 $dad_wait_time)
+                do
+                    # We're going to poll for the tentative flag every second.
+                    sleep 1
+                    ${ip} addr show ${interface} | grep inet6 | grep tentative \
+                        &> /dev/null
+                    if [ $? -ne 0 ]; then
+                        break;
+                    fi
+                done
+            fi
+        fi
+
+        ;;
+
+    BOUND6|RENEW6|REBIND6)
+        if [ "${new_ip6_address}" ] && [ "${new_ip6_prefixlen}" ]; then
+            # set leased IP
+            ${ip} -6 addr add ${new_ip6_address}/${new_ip6_prefixlen} \
+                dev ${interface} scope global
+        fi
+
+        # update /etc/resolv.conf
+        if [ "${reason}" = BOUND6 ] ||
+           [ "${new_dhcp6_name_servers}" != "${old_dhcp6_name_servers}" ] ||
+           [ "${new_dhcp6_domain_search}" != "${old_dhcp6_domain_search}" ]; then
+            make_resolv_conf
+        fi
+
+        ;;
+
+    DEPREF6)
+        if [ -z "${cur_ip6_prefixlen}" ]; then
+            exit 2
+        fi
+
+        # set preferred lifetime of leased IP to 0
+        ${ip} -6 addr change ${cur_ip6_address}/${cur_ip6_prefixlen} \
+            dev ${interface} scope global preferred_lft 0
+
+        ;;
+
+    EXPIRE6|RELEASE6|STOP6)
+        if [ -z "${old_ip6_address}" ] || [ -z "${old_ip6_prefixlen}" ]; then
+            exit 2
+        fi
+
+        # delete leased IP
+        ${ip} -6 addr del ${old_ip6_address}/${old_ip6_prefixlen} \
+            dev ${interface}
+
+        ;;
+esac
+
+exit 0
diff --git a/hooks/dhclient-start.in b/hooks/dhclient-start.in
new file mode 100755
index 000000000..29a33645d
--- /dev/null
+++ b/hooks/dhclient-start.in
@@ -0,0 +1,13 @@
+#! /bin/bash
+
+set -e
+
+LXC_HOOK_DIR="@LXCHOOKDIR@"
+
+rootfs="${LXC_ROOTFS_PATH##*:}"
+pidfile="${rootfs%/*}/dhclient.pid"
+
+mkdir -p "${rootfs}/var/lib/dhclient"
+
+nsenter -u -U -n -t "${LXC_PID}" -- \
+  /sbin/dhclient -nw -1 -pf ${pidfile} -lf ${rootfs}/var/lib/dhclient/dhclient.leases -e ROOTFS=${rootfs} -sf ${LXC_HOOK_DIR}/dhclient-script
diff --git a/hooks/dhclient-stop.in b/hooks/dhclient-stop.in
new file mode 100755
index 000000000..63998aee5
--- /dev/null
+++ b/hooks/dhclient-stop.in
@@ -0,0 +1,15 @@
+#! /bin/bash
+
+set -e
+
+LXC_HOOK_DIR="@LXCHOOKDIR@"
+
+rootfs="${LXC_ROOTFS_PATH##*:}"
+pidfile="${rootfs%/*}/dhclient.pid"
+
+# XXX Stop hook namespace arguments are wrong for some reason, those are the host namespaces not the container ones.
+# Retrieve the namespaces from the dhclient pidfile instead.
+nsenter -u -U -n -t $(< ${pidfile}) -- \
+  /sbin/dhclient -r -pf ${pidfile} -lf ${rootfs}/var/lib/dhclient/dhclient.leases -e ROOTFS=${rootfs} -sf ${LXC_HOOK_DIR}/dhclient-script
+
+rm -f ${pidfile}


More information about the lxc-devel mailing list