Browse Source

Add dhcp-client state, with VRF support

Signed-off-by: Maximilian Wilhelm <max@sdn.clinic>
Maximilian Wilhelm 4 months ago
parent
commit
79e07f458c
3 changed files with 505 additions and 0 deletions
  1. 439 0
      dhcp-client/dhclient-script
  2. 54 0
      dhcp-client/dhclient.conf
  3. 12 0
      dhcp-client/init.sls

+ 439 - 0
dhcp-client/dhclient-script

@@ -0,0 +1,439 @@
+#!/bin/sh
+
+# dhclient-script for Linux. Dan Halbert, March, 1997.
+# Updated for Linux 2.[12] by Brian J. Murrell, January 1999.
+# Modified for Debian.  Matt Zimmerman and Eloy Paris, December 2003
+# Modified to remove useless tests for antiquated kernel versions that
+# this doesn't even work with anyway, and introduces a dependency on /usr
+# being mounted, which causes cosmetic errors on hosts that NFS mount /usr
+# Andrew Pollock, February 2005
+# Modified to work on point-to-point links. Andrew Pollock, June 2005
+# Modified to support passing the parameters called with to the hooks. Andrew Pollock, November 2005
+
+# The alias handling in here probably still sucks. -mdz
+
+# wait for given file to be writable
+wait_for_rw() {
+    local file=$1
+    # Find out whether we are going to mount / rw
+    exec 9>&0 </etc/fstab
+    rootmode=rw
+    while read dev mnt type opts dump pass junk; do
+        [ "$mnt" != / ] && continue
+        case "$opts" in
+            ro|ro,*|*,ro|*,ro,*)
+               rootmode=ro
+               ;;
+             esac
+     done
+     exec 0>&9 9>&-
+
+    # Wait for $file to become writable
+    if [ "$rootmode" = "rw" ]; then
+        while ! { : >> "$file"; } 2>/dev/null; do
+            sleep 0.1
+        done
+    fi
+}
+
+# update /etc/resolv.conf based on received values
+make_resolv_conf() {
+    local new_resolv_conf
+
+    # DHCPv4
+    if [ -n "$new_domain_search" ] || [ -n "$new_domain_name" ] ||
+       [ -n "$new_domain_name_servers" ]; then
+        resolv_conf=$(readlink -f "/etc/resolv.conf" 2>/dev/null) ||
+            resolv_conf="/etc/resolv.conf"
+
+        new_resolv_conf="${resolv_conf}.dhclient-new.$$"
+        wait_for_rw "$new_resolv_conf"
+        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
+        resolv_conf=$(readlink -f "/etc/resolv.conf" 2>/dev/null) ||
+            resolv_conf="/etc/resolv.conf"
+
+        new_resolv_conf="${resolv_conf}.dhclient-new.$$"
+        wait_for_rw "$new_resolv_conf"
+        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() {
+    if [ -n "$new_host_name" ]; then
+        local 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" != "$current_host_name" ]; then
+               hostname "$new_host_name"
+           fi
+        fi
+    fi
+}
+
+# run given script
+run_hook() {
+    local script="$1"
+    local exit_status=0
+
+    if [ -f $script ]; then
+        . $script
+        exit_status=$?
+    fi
+
+    if [ -n "$exit_status" ] && [ "$exit_status" -ne 0 ]; then
+        logger -p daemon.err "$script returned non-zero exit status $exit_status"
+    fi
+
+    return $exit_status
+}
+
+# run scripts in given directory
+run_hookdir() {
+    local dir="$1"
+    local exit_status=0
+
+    if [ -d "$dir" ]; then
+        for script in $(run-parts --list $dir); do
+            run_hook $script
+            exit_status=$((exit_status|$?))
+        done
+    fi
+
+    return $exit_status
+}
+
+# Must be used on exit.   Invokes the local dhcp client exit hooks, if any.
+exit_with_hooks() {
+    local exit_status=$1
+
+    # Source the documented exit-hook script, if it exists
+    if ! run_hook /etc/dhcp/dhclient-exit-hooks; then
+        exit_status=$?
+    fi
+
+    # Now run scripts in the Debian-specific directory.
+    if ! run_hookdir /etc/dhcp/dhclient-exit-hooks.d; then
+        exit_status=$?
+    fi
+
+    exit $exit_status
+}
+
+
+# The 576 MTU is only used for X.25 and dialup connections
+# where the admin wants low latency.  Such a low MTU can cause
+# problems with UDP traffic, among other things.  As such,
+# disallow MTUs from 576 and below by default, so that broken
+# MTUs are ignored, but higher stuff is allowed (1492, 1500, etc).
+if [ -z "$new_interface_mtu" ] || [ "$new_interface_mtu" -le 576 ]; then
+    new_interface_mtu=''
+fi
+
+
+# The action starts here
+
+# Invoke the local dhcp client enter hooks, if they exist.
+run_hook /etc/dhcp/dhclient-enter-hooks
+run_hookdir /etc/dhcp/dhclient-enter-hooks.d
+
+# 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} \
+                ${new_dhcp_lease_time:+valid_lft $new_dhcp_lease_time} \
+                ${new_dhcp_lease_time:+preferred_lft $new_dhcp_lease_time} \
+                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
+
+		    # Is the interface part of a VRF?
+		    # inspired by https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=964264
+		    vrf_table="$(ip -d link show dev ${interface} | awk '/vrf_slave/ { print $3 }')"
+		    vrf_cfg=""
+		    if [ "${vrf_table}" ]; then
+			vrf_cfg="table ${vrf_table}"
+		    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 $vrf_cfg >/dev/null 2>&1
+			fi
+
+			# set default route
+			ip -4 route add default via ${router} dev ${interface} $vrf_cfg \
+			    ${if_metric:+metric $if_metric} >/dev/null 2>&1
+
+			if [ -n "$if_metric" ]; then
+			    if_metric=$((if_metric+1))
+			fi
+		    done
+	    fi
+        else # RENEW||REBIND
+            ip -4 addr change ${new_ip_address}${new_subnet_mask:+/$new_subnet_mask} \
+                ${new_broadcast_address:+broadcast $new_broadcast_address} \
+                ${new_dhcp_lease_time:+valid_lft $new_dhcp_lease_time} \
+                ${new_dhcp_lease_time:+preferred_lft $new_dhcp_lease_time} \
+                dev ${interface} label ${interface}
+        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} \
+            ${new_dhcp_lease_time:+valid_lft $new_dhcp_lease_time} \
+            ${new_dhcp_lease_time:+preferred_lft $new_dhcp_lease_time} \
+            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_with_hooks 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
+
+        # flush any stale global permanent IPs from interface
+        ip -6 addr flush dev ${interface} scope global permanent
+
+        ;;
+
+    BOUND6|RENEW6|REBIND6)
+        if [ "${new_ip6_address}" ]; then
+            # set leased IP
+            ip -6 addr replace ${new_ip6_address} \
+                dev ${interface} scope global valid_lft ${new_max_life} \
+                preferred_lft ${new_preferred_life}
+        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)
+        # set preferred lifetime of leased IP to 0
+        ip -6 addr change ${cur_ip6_address} \
+            dev ${interface} scope global preferred_lft 0
+
+        ;;
+
+    EXPIRE6|RELEASE6|STOP6)
+        if [ -z "${old_ip6_address}" ]; then
+            exit_with_hooks 2
+        fi
+
+        # delete leased IP
+        ip -6 addr del ${old_ip6_address} \
+            dev ${interface}
+
+        ;;
+esac
+
+exit_with_hooks 0

+ 54 - 0
dhcp-client/dhclient.conf

@@ -0,0 +1,54 @@
+# Configuration file for /sbin/dhclient.
+#
+# This is a sample configuration file for dhclient. See dhclient.conf's
+#	man page for more information about the syntax of this file
+#	and a more comprehensive list of the parameters understood by
+#	dhclient.
+#
+# Normally, if the DHCP server provides reasonable information and does
+#	not leave anything out (like the domain name, for example), then
+#	few changes must be made to this file, if any.
+#
+
+option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
+
+send host-name = gethostname();
+request subnet-mask, broadcast-address, time-offset, routers,
+	domain-name, domain-name-servers, domain-search, host-name,
+	dhcp6.name-servers, dhcp6.domain-search, dhcp6.fqdn, dhcp6.sntp-servers,
+	netbios-name-servers, netbios-scope, interface-mtu,
+	rfc3442-classless-static-routes, ntp-servers;
+
+#send dhcp-client-identifier 1:0:a0:24:ab:fb:9c;
+#send dhcp-lease-time 3600;
+#supersede domain-name "fugue.com home.vix.com";
+#prepend domain-name-servers 127.0.0.1;
+#require subnet-mask, domain-name-servers;
+#timeout 60;
+#retry 60;
+#reboot 10;
+#select-timeout 5;
+#initial-interval 2;
+script "/usr/local/sbin/dhclient-script";
+#media "-link0 -link1 -link2", "link0 link1";
+#reject 192.33.137.209;
+
+#alias {
+#  interface "eth0";
+#  fixed-address 192.5.5.213;
+#  option subnet-mask 255.255.255.255;
+#}
+
+#lease {
+#  interface "eth0";
+#  fixed-address 192.33.137.200;
+#  medium "link0 link1";
+#  option host-name "andare.swiftmedia.com";
+#  option subnet-mask 255.255.255.0;
+#  option broadcast-address 192.33.137.255;
+#  option routers 192.33.137.250;
+#  option domain-name-servers 127.0.0.1;
+#  renew 2 2000/1/12 00:00:01;
+#  rebind 2 2000/1/12 00:00:01;
+#  expire 2 2000/1/12 00:00:01;
+#}

+ 12 - 0
dhcp-client/init.sls

@@ -0,0 +1,12 @@
+#
+# DHCP client w/ VRF support
+#
+
+/etc/dhcp/dhclient.conf:
+  file.managed:
+    - source: salt://dhcp-client/dhclient.conf
+
+/usr/local/sbin/dhclient-script:
+  file.managed:
+    - source: salt://dhcp-client/dhclient-script
+    - mode: 755