]> git.proxmox.com Git - mirror_lxc.git/commitdiff
hooks: add dhclient hooks
authorJonathan Calmels <jcalmels@nvidia.com>
Wed, 8 Nov 2017 10:58:01 +0000 (05:58 -0500)
committerJonathan Calmels <jcalmels@nvidia.com>
Tue, 21 Nov 2017 00:54:16 +0000 (16:54 -0800)
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@nvidia.com>
.gitignore
configure.ac
hooks/Makefile.am
hooks/dhclient-script [new file with mode: 0644]
hooks/dhclient-start.in [new file with mode: 0755]
hooks/dhclient-stop.in [new file with mode: 0755]

index 892489cc959de7b0d47b7a8e5c7e0c897d7adcf5..8385ef79327b0abb5222993e1e9e8bfa215a9121 100644 (file)
@@ -136,6 +136,8 @@ doc/manpage.refs
 doc/api/html/*
 
 hooks/unmount-namespace
+hooks/dhclient-start
+hooks/dhclient-stop
 
 m4/
 
index 63df7466cb879a0b2f6e2666d48aaa276bcd2731..eb6fc39c527aeab9554a8e3bc74bd638878022a9 100644 (file)
@@ -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
index 23b85c8f31aaeccdc321716798bd07cd67704958..b8b8f532d4b19a6d4714f4fbe2c5c4f469a56ccb 100644 (file)
@@ -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 (file)
index 0000000..171117f
--- /dev/null
@@ -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 (executable)
index 0000000..29a3364
--- /dev/null
@@ -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 (executable)
index 0000000..63998ae
--- /dev/null
@@ -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}