From e39f2ead18c17fa4a8fdfe35437202fb202e983a Mon Sep 17 00:00:00 2001 From: zsdc Date: Wed, 12 Feb 2020 13:52:12 +0200 Subject: dhclient-script: T1987: Multiple fixes in dhclient-script This changeset contains multiple changes in structure, logic, and bugfixes for dhclient-script. It should provide better compatibility with new Debian versions and flexibility in controlling and changing VyOS-related functions. 1. Structure change: * All VyOS-related functionality was moved from dhclient-script itself to separated hook files. * Old vyatta-dhclient-hook was moved from vyatta-cfg to vyos-1x. * This change allows discard dhclient-script replacing and use the original one from Debian without any changes. So, we do not need to track all changes in upstream so carefully. * To provide compatibility between original dhclient-script and VyOS, two internal commands/functions are repaced in hooks: ip and make_resolv_conf. So, in all places where used ${ip} or make_resolv_conf, actually using VyOS-tuned functions instead original. * `ip` function is a wrapper, which automatically chooses what to use: transparently pass a command to /usr/sbin/ip, change a route in kernel table or FRRouting config via vtysh. * `make_resolv_conf` function main logic was copied from current VyOS implementation and use vyos-hostsd-client for making changes 2. Added: * Logging. Now is possible to log all changes, what is doing by dhclient-script. Logs can be saved to the journal and displayed in stderr (for debugging purposes). By default, logging to the journal is enabled (at least for some time) to provide a way to collect enough information in case if some bug in this new implementation will be found. This can be changed in the 01-vyos-logging file. 3. Fixed/Changed: * If DHCP lease was expired, released or dhclient was stopped, dhclient-script will try to delete default route from this lease. * Instead of blindly killing all dhclients in case if FRRouting daemon is not running, now used more intelligent logic: * dhclients are stopping natively (with all triggers processing), instead of killing; * dhclient-script will not kill parent dhclient process. This allows to fix the problem when systemd inform about failing to rise up interfaces at early boot stages (used in Cloud-init images); * dhclient-script will not touch dhclients, which are not related to the current interface or IP protocol version. * For getting FRRouting daemon status used native way via watchfrr.sh, instead of the previous trick with vtysh accessibility. * before adding a new route to FRRouting configuration, this route will be deleted from the kernel (if it is presented there). This allows to properly replace routes, added at early boot stages, when FRR not available. * Routes in FRRouting are adding with "tag 210". This allows protecting static routes, added via CLI, from deletion when old routes are deleting by DHCP. * DNS servers will be reconfigured only when $new_domain_name_servers are not the same as $old_domain_name_servers. Previously, this was done during each RENEW procedure. * Replacing MTU for preconfigured one was changed to Python (via vyos.config). The previous version with vyatta-interfaces.pl was obsoleted and seems to be broken. --- debian/vyos-1x.install | 1 + .../dhcp/dhclient-enter-hooks.d/01-vyos-logging | 20 ++++++ .../dhclient-enter-hooks.d/02-vyos-stopdhclient | 27 ++++++++ .../dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper | 76 ++++++++++++++++++++++ .../dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf | 39 +++++++++++ .../dhcp/dhclient-enter-hooks.d/05-vyos-mtureplace | 38 +++++++++++ src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup | 16 +++++ .../dhclient-exit-hooks.d/vyatta-dhclient-hook | 39 +++++++++++ 8 files changed, 256 insertions(+) create mode 100644 src/etc/dhcp/dhclient-enter-hooks.d/01-vyos-logging create mode 100644 src/etc/dhcp/dhclient-enter-hooks.d/02-vyos-stopdhclient create mode 100644 src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper create mode 100644 src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf create mode 100644 src/etc/dhcp/dhclient-enter-hooks.d/05-vyos-mtureplace create mode 100644 src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup create mode 100644 src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install index dfa6ab0f9..e1236b75b 100644 --- a/debian/vyos-1x.install +++ b/debian/vyos-1x.install @@ -1,6 +1,7 @@ etc/init.d etc/vyos etc/systemd +etc/dhcp lib/ opt/ usr/ diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/01-vyos-logging b/src/etc/dhcp/dhclient-enter-hooks.d/01-vyos-logging new file mode 100644 index 000000000..121fb21be --- /dev/null +++ b/src/etc/dhcp/dhclient-enter-hooks.d/01-vyos-logging @@ -0,0 +1,20 @@ +# enable logging +LOG_ENABLE="yes" +LOG_STDERR="no" +LOG_TAG="dhclient-script-vyos" + +function logmsg () { + # log message to journal + case $1 in + error) LOG_PRIO="daemon.err" ;; + info) LOG_PRIO="daemon.info" ;; + esac + + if [ "${LOG_ENABLE}" == "yes" ] ; then + if [ "${LOG_STDERR}" == "yes" ] ; then + /usr/bin/logger -e --id=$$ -s -p ${LOG_PRIO} -t ${LOG_TAG} "${@:2}" + else + /usr/bin/logger -e --id=$$ -p ${LOG_PRIO} -t ${LOG_TAG} "${@:2}" + fi + fi +} diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/02-vyos-stopdhclient b/src/etc/dhcp/dhclient-enter-hooks.d/02-vyos-stopdhclient new file mode 100644 index 000000000..d5d90632c --- /dev/null +++ b/src/etc/dhcp/dhclient-enter-hooks.d/02-vyos-stopdhclient @@ -0,0 +1,27 @@ +# skip all of this if dhclient-script running by stop command defined below +if [ -z ${CONTROLLED_STOP} ] ; then + # stop dhclient for this interface, if it is not current one + # get PID for current dhclient + current_dhclient=`ps --no-headers --format ppid --pid $$ | awk '{ print $1 }'` + + # get PID for master process (current can be a fork) + master_dhclient=`ps --no-headers --format ppid --pid $current_dhclient | awk '{ print $1 }'` + + # get IP version for current dhclient + ipversion_arg=`ps --no-headers --format args --pid $current_dhclient | awk '{ print $2 }'` + + # get list of all dhclient running for current interface + dhclients_pids=(`ps --no-headers --format pid,args -C dhclient | awk -v IFACE="/sbin/dhclient $ipversion_arg .*$interface$" '$0 ~ IFACE { print $1 }'`) + + logmsg info "Current dhclient PID: $current_dhclient, Parent PID: $master_dhclient, IP version: $ipversion_arg, All dhclients for interface $interface: ${dhclients_pids[@]}" + # stop all dhclients for current interface, except current one + for dhclient in ${dhclients_pids[@]}; do + if ([ $dhclient -ne $current_dhclient ] && [ $dhclient -ne $master_dhclient ]); then + logmsg info "Stopping dhclient with PID: ${dhclient}" + # get path to PID-file of dhclient process + local dhclient_pidfile=`ps --no-headers --format args --pid $dhclient | awk 'match($0, ".*-pf (/.*pid) .*", PF) { print PF[1] }'` + # stop dhclient with native command - this will run dhclient-script with correct reason unlike simple kill + dhclient -e CONTROLLED_STOP=yes -x -pf $dhclient_pidfile + fi + done +fi diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper new file mode 100644 index 000000000..59f92703c --- /dev/null +++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper @@ -0,0 +1,76 @@ +# redefine ip command to use FRR when it is available + +# get status of FRR +function frr_alive () { + /usr/lib/frr/watchfrr.sh all_status + if [ "$?" -eq "0" ] ; then + logmsg info "FRR status: running" + return 0 + else + logmsg info "FRR status: not running" + return 1 + fi +} + +# convert ip route command to vtysh +function iptovtysh () { + # prepare variables for vtysh command + VTYSH_DISTANCE="210" + VTYSH_TAG="210" + # convert default route to 0.0.0.0/0 + if [ "$4" == "default" ] ; then + VTYSH_NETADDR="0.0.0.0/0" + else + VTYSH_NETADDR=$4 + fi + # add /32 to ip addresses without netmasks + if [[ ! $VTYSH_NETADDR =~ ^.*/[[:digit:]]+$ ]] ; then + VTYSH_NETADDR="$VTYSH_NETADDR/32" + fi + # get gateway address + if [ "$5" == "via" ] ; then + VTYSH_GATEWAY=$6 + fi + # get device name + if [ "$5" == "dev" ]; then + VTYSH_DEV=$6 + elif [ "$7" == "dev" ]; then + VTYSH_DEV=$8 + fi + VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE" + # delete route if the command is "del" + if [ "$3" == "del" ] ; then + VTYSH_CMD="no $VTYSH_CMD" + fi + logmsg info "Converted vtysh command: \"$VTYSH_CMD\"" +} + +# delete the same route from kernel before adding new one +function delroute () { + logmsg info "Checking if the route presented in kernel: $@" + if /usr/sbin/ip route show $@ | grep -qx "$1 " ; then + logmsg info "Deleting IP route: \"/usr/sbin/ip route del $@\"" + /usr/sbin/ip route del $@ + fi +} + +# replace ip command with this wrapper +function ip () { + # pass comand to system `ip` if this is not related to routes change + if [ "$2" != "route" ] ; then + logmsg info "Passing command to /usr/sbin/ip: \"$@\"" + /usr/sbin/ip $@ + else + # if we want to work with routes, try to use FRR first + if frr_alive ; then + delroute ${@:4} + iptovtysh $@ + logmsg info "Sending command to vtysh" + vtysh -c "conf t" -c "$VTYSH_CMD" + else + # add ip route to kernel + logmsg info "Modifying routes in kernel: \"/usr/sbin/ip $@\"" + /usr/sbin/ip $@ + fi + fi +} diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf b/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf new file mode 100644 index 000000000..ea5562ea8 --- /dev/null +++ b/src/etc/dhcp/dhclient-enter-hooks.d/04-vyos-resolvconf @@ -0,0 +1,39 @@ +# modified make_resolv_conf () for Vyatta system below +make_resolv_conf() { + if [ -n "$new_domain_name" ]; then + logmsg info "Adding search-domain \"$new_domain_name\" via vyos-hostsd-client" + /usr/bin/vyos-hostsd-client --set-host-name --search-domain $new_domain_name + fi + + if [ -n "$new_dhcp6_domain_search" ]; then + logmsg info "Adding search-domain \"$new_dhcp6_domain_search\" via vyos-hostsd-client" + /usr/bin/vyos-hostsd-client --set-host-name --search-domain $new_dhcp6_domain_search + fi + + if [ -n "$new_domain_name_servers" ] && ! cli-shell-api existsEffective system disable-dhcp-nameservers && [ "$new_domain_name_servers" != "$old_domain_name_servers" ] ; then + logmsg info "Deleting nameservers with tag \"dhcp-$interface\" via vyos-hostsd-client" + vyos-hostsd-client --delete-name-servers --tag dhcp-$interface + NEW_SERVERS="" + for nameserver in $new_domain_name_servers; do + NEW_SERVERS="$NEW_SERVERS --name-server $nameserver" + done + logmsg info "Adding nameservers \"$NEW_SERVERS\" with tag \"dhcp-$interface\" via vyos-hostsd-client" + /usr/bin/vyos-hostsd-client --add-name-servers $NEW_SERVERS --tag dhcp-$interface + fi + + if [ -n "$new_dhcp6_name_servers" ] && ! cli-shell-api existsEffective system disable-dhcp-nameservers && [ "$new_dhcp6_name_servers" != "$old_dhcp6_name_servers" ] ; then + logmsg info "Deleting nameservers with tag \"dhcpv6-$interface\" via vyos-hostsd-client" + vyos-hostsd-client --delete-name-servers --tag dhcpv6-$interface + NEW_SERVERS="" + for nameserver in $new_dhcp6_name_servers; do + NEW_SERVERS="$NEW_SERVERS --name-server $nameserver" + done + logmsg info "Adding nameservers \"$NEW_SERVERS\" with tag \"dhcpv6-$interface\" via vyos-hostsd-client" + /usr/bin/vyos-hostsd-client --add-name-servers $NEW_SERVERS --tag dhcpv6-$interface + fi + + if cli-shell-api existsEffective service dns forwarding; then + logmsg info "Enabling DNS forwarding" + /usr/libexec/vyos/conf_mode/dns_forwarding.py --dhclient + fi +} diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/05-vyos-mtureplace b/src/etc/dhcp/dhclient-enter-hooks.d/05-vyos-mtureplace new file mode 100644 index 000000000..4a08765ba --- /dev/null +++ b/src/etc/dhcp/dhclient-enter-hooks.d/05-vyos-mtureplace @@ -0,0 +1,38 @@ +# replace MTU with value from configuration + +# get MTU value via Python +# as configuration is not available to cli-shell-api at the first boot, we must use vyos.config, which contain workaround for this, instead clean shell +function get_mtu { +python3 - <\w+)\.(?P\d+)$') +if regex_filter.search(interface_name): + iface = regex_filter.search(interface_name).group('interface') + vid = regex_filter.search(interface_name).group('vid') + interface_name = "{} vif {}".format(iface, vid) + +# initialize config +config = Config() +if config.exists('interfaces'): + iface_types = config.list_nodes('interfaces') + for iface_type in iface_types: + # check if configuration contain MTU value for interface and return (print) it + if config.exists("interfaces {} {} mtu".format(iface_type, interface_name)): + print(format(config.return_value("interfaces {} {} mtu".format(iface_type, interface_name)))) +PYEND +} + +# check if DHCP server return MTU value +if [ -n "$new_interface_mtu" ]; then + # try to get MTU from config and replace original one + configured_mtu="$(get_mtu)" + if [[ -n $configured_mtu ]] ; then + logmsg info "Replacing MTU value for $interface with preconfigured one: $configured_mtu" + new_interface_mtu="$configured_mtu" + fi +fi diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup new file mode 100644 index 000000000..ce846f6c3 --- /dev/null +++ b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup @@ -0,0 +1,16 @@ +if [[ $reason =~ (EXPIRE|FAIL|RELEASE|STOP) ]]; then + # delete dynamic nameservers from a configuration if lease was deleted + logmsg info "Deleting nameservers with tag \"dhcp-${interface}\" via vyos-hostsd-client" + vyos-hostsd-client --delete-name-servers --tag dhcp-${interface} + # try to delete default ip route (NOTE: here we use 'ip' wrapper, therefore a route will be actually deleted via /usr/sbin/ip or vtysh, according to the system state) + for router in $old_routers; do + logmsg info "Deleting default route: via $router dev ${interface}" + ip -4 route del default via $router dev ${interface} + done +fi + +if [[ $reason =~ (EXPIRE6|RELEASE6|STOP6) ]]; then + # delete dynamic nameservers from a configuration if lease was deleted + logmsg info "Deleting nameservers with tag \"dhcpv6-${interface}\" via vyos-hostsd-client" + vyos-hostsd-client --delete-name-servers --tag dhcpv6-${interface} +fi diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook new file mode 100644 index 000000000..dcd06644f --- /dev/null +++ b/src/etc/dhcp/dhclient-exit-hooks.d/vyatta-dhclient-hook @@ -0,0 +1,39 @@ +#!/bin/sh + +# Author: Stig Thormodsrud +# Date: 2007 +# Description: dhcp client hook + +# **** License **** +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# This code was originally developed by Vyatta, Inc. +# Portions created by Vyatta are Copyright (C) 2006, 2007, 2008 Vyatta, Inc. +# All Rights Reserved. +# **** End License **** + +# To enable this script set the following variable to "yes" +RUN="yes" + +if [ "$RUN" = "yes" ]; then + LOG=/var/lib/dhcp/dhclient_"$interface"_lease + echo `date` > $LOG + + for i in reason interface new_expiry new_dhcp_lease_time medium \ + alias_ip_address new_ip_address new_broadcast_address \ + new_subnet_mask new_domain_name new_network_number \ + new_domain_name_servers new_routers new_static_routes \ + new_dhcp_server_identifier new_dhcp_message_type \ + old_ip_address old_subnet_mask old_domain_name \ + old_domain_name_servers old_routers \ + old_static_routes; do + echo $i=\'${!i}\' >> $LOG + done +fi -- cgit v1.2.3