diff options
author | William A. Kennington III <wak@google.com> | 2022-05-17 02:19:11 +0300 |
---|---|---|
committer | William A. Kennington III <wak@google.com> | 2022-05-27 02:02:20 +0300 |
commit | 37e6f396313e8b4b9c7827fbdcce259b97a37184 (patch) | |
tree | f3daa65c3f20a1c1aa5ec389401710597d0efeef /meta-google | |
parent | 757cba278a0aa5fa95f38ac80950e5d055e4b224 (diff) | |
download | openbmc-37e6f396313e8b4b9c7827fbdcce259b97a37184.tar.xz |
meta-google: gbmc-bridge: Rework IP address persistence
This consolidates all of the mechanisms which write out a persistent IP
into a single place. It also transitions to writing a very simple
persistent file instead of systemd style network units.
Change-Id: Ib99d7646178d2c5383cf23b09248bf24544c1d9e
Signed-off-by: William A. Kennington III <wak@google.com>
Diffstat (limited to 'meta-google')
-rw-r--r-- | meta-google/recipes-google/ncsi/files/50-gbmc-ncsi-clear-ip.sh.in (renamed from meta-google/recipes-google/ncsi/files/25-gbmc-ncsi-clear-ip.sh.in) | 15 | ||||
-rw-r--r-- | meta-google/recipes-google/ncsi/files/gbmc-ncsi-br-pub-addr.sh.in | 64 | ||||
-rwxr-xr-x | meta-google/recipes-google/ncsi/files/gbmc-ncsi-ip-from-ra.sh.in | 47 | ||||
-rw-r--r-- | meta-google/recipes-google/ncsi/gbmc-ncsi-config.bb | 14 | ||||
-rw-r--r-- | meta-google/recipes-google/networking/gbmc-bridge.bb | 7 | ||||
-rw-r--r-- | meta-google/recipes-google/networking/gbmc-bridge/-bmc-gbmcbr.network.in | 3 | ||||
-rw-r--r-- | meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-dhcp.sh | 65 | ||||
-rw-r--r-- | meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-lib.sh | 133 | ||||
-rw-r--r-- | meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-load-ip.service | 9 |
9 files changed, 203 insertions, 154 deletions
diff --git a/meta-google/recipes-google/ncsi/files/25-gbmc-ncsi-clear-ip.sh.in b/meta-google/recipes-google/ncsi/files/50-gbmc-ncsi-clear-ip.sh.in index e17a5e2009..5056cfdc9c 100644 --- a/meta-google/recipes-google/ncsi/files/25-gbmc-ncsi-clear-ip.sh.in +++ b/meta-google/recipes-google/ncsi/files/50-gbmc-ncsi-clear-ip.sh.in @@ -12,15 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -[ -z "${gbmc_ncsi_clear_ip-}" ] || exit +[ -n "${gbmc_ncsi_clear_ip-}" ] && return source /usr/libexec/ncsid_lib.sh || exit gbmc_ncsi_clear_ip_hook() { - UpdateIP xyz.openbmc_project.Network '@NCSI_IF@' '0.0.0.0' '0' || true - UpdateIP xyz.openbmc_project.Network '@NCSI_IF@' '::' '0' || true + local ip="${1-}" + + # We only want to clear our IPs if we are assigning a new IP + [ -z "$ip" ] && return + + echo "Removing Persistent NCSI IPs" >&2 + SetStatic xyz.openbmc_project.Network '@NCSI_IF@' 2>/dev/null || true + UpdateIP xyz.openbmc_project.Network '@NCSI_IF@' '0.0.0.0' '0' 2>/dev/null || true + UpdateIP xyz.openbmc_project.Network '@NCSI_IF@' '::' '0' 2>/dev/null || true } -GBMC_BR_DHCP_HOOKS+=(gbmc_ncsi_clear_ip_hook) +GBMC_BR_LIB_SET_IP_HOOKS+=(gbmc_ncsi_clear_ip_hook) gbmc_ncsi_clear_ip=1 diff --git a/meta-google/recipes-google/ncsi/files/gbmc-ncsi-br-pub-addr.sh.in b/meta-google/recipes-google/ncsi/files/gbmc-ncsi-br-pub-addr.sh.in index 9f008a9a44..1992dd1a69 100644 --- a/meta-google/recipes-google/ncsi/files/gbmc-ncsi-br-pub-addr.sh.in +++ b/meta-google/recipes-google/ncsi/files/gbmc-ncsi-br-pub-addr.sh.in @@ -12,7 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -[ -z "${gbmc_ncsi_br_pub_addr_lib-}" ] || return +[ -n "${gbmc_ncsi_br_pub_addr_lib-}" ] && return + +[ ! -e /usr/share/gbmc-br-lib.sh ] && return + +source /usr/share/network/lib.sh || exit +source /usr/share/gbmc-br-lib.sh || exit gbmc_ncsi_br_pub_addr_init= gbmc_ncsi_br_pub_addr_lastip= @@ -44,55 +49,16 @@ gbmc_ncsi_br_pub_addr_update() { local contents= if (( ${#pfx_bytes[@]} != 0 )); then pfx_bytes[8]=0xfd - # Save our old prefix assuming we have one - local old_offset="${pfx_bytes[9]}" - if (( old_offset == 0 )); then - old_offset=0x01 - else - pfx_bytes[9]=0x00 + # We never want to use the stateless pfx + if (( pfx_bytes[9] == 0 )); then + pfx_bytes[9]=0x01 fi - local stateless_pfx="$(ip_bytes_to_str pfx_bytes)" - pfx_bytes[9]="$old_offset" - local ncsi_pfx="$(ip_bytes_to_str pfx_bytes)" - read -r -d '' contents <<EOF -[Network] -Address=$ncsi_pfx/128 -IPv6PrefixDelegation=yes -[IPv6PrefixDelegation] -RouterLifetimeSec=60 -[IPv6Prefix] -Prefix=$stateless_pfx/80 -PreferredLifetimeSec=60 -ValidLifetimeSec=60 -[IPv6RoutePrefix] -Route=$ncsi_pfx/80 -LifetimeSec=60 -[Route] -Destination=$stateless_pfx/76 -Type=unreachable -Metric=1024 -EOF - # Delete DHCP configured addresses if we have a host published address - rm -f /etc/systemd/network/{00,}-bmc-gbmcbr.network.d/50-public.conf - fi - - local file - for file in /run/systemd/network/{00,}-bmc-gbmcbr.network.d/50-public.conf; do - mkdir -p -m 755 "$(dirname "$file")" - if [ -z "$contents" ]; then - rm -f "$file" - else - printf '%s' "$contents" >"$file" - fi - done - - # Ensure that systemd-networkd performs a reconfiguration as it doesn't - # currently check the mtime of drop-in files. - touch -c /lib/systemd/network/*-bmc-gbmcbr.network - - if [ "$(systemctl is-active systemd-networkd)" != 'inactive' ]; then - networkctl reload - networkctl reconfigure gbmcbr + # Remove any existing persisted IP + gbmc_br_set_ip + # Load the IP to the bridge non-persistently + gbmc_br_reload_ip "$(ip_bytes_to_str pfx_bytes)" + else + gbmc_br_reload_ip fi } diff --git a/meta-google/recipes-google/ncsi/files/gbmc-ncsi-ip-from-ra.sh.in b/meta-google/recipes-google/ncsi/files/gbmc-ncsi-ip-from-ra.sh.in index 6255f70858..5daa1527a3 100755 --- a/meta-google/recipes-google/ncsi/files/gbmc-ncsi-ip-from-ra.sh.in +++ b/meta-google/recipes-google/ncsi/files/gbmc-ncsi-ip-from-ra.sh.in @@ -13,8 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +[ ! -e /usr/share/gbmc-br-lib.sh ] && exit + source /usr/share/network/lib.sh || exit -source /usr/libexec/ncsid_lib.sh || exit +source /usr/share/gbmc-br-lib.sh || exit NCSI_IF='@NCSI_IF@' @@ -44,45 +46,8 @@ set_net() { # We no longer need NCSId if we are in this configuration systemctl stop --no-block ncsid@"$NCSI_IF" || true - # Delete any stale IP Addresses from the primary interface as we won't use them - UpdateIP xyz.openbmc_project.Network "$NCSI_IF" '0.0.0.0' '0' || true - UpdateIP xyz.openbmc_project.Network "$NCSI_IF" '::' '0' || true - - read -r -d '' contents <<EOF -[Network] -Address=$pfx/128 -IPv6PrefixDelegation=yes -[IPv6PrefixDelegation] -RouterLifetimeSec=60 -[IPv6Prefix] -Prefix=$stateless_pfx/80 -PreferredLifetimeSec=60 -ValidLifetimeSec=60 -[IPv6RoutePrefix] -Route=$pfx/80 -LifetimeSec=60 -[Route] -Destination=$stateless_pfx/76 -Type=unreachable -Metric=1024 -EOF - for file in /run/systemd/network/{00,}-bmc-gbmcbr.network.d/49-public-ra.conf; do - mkdir -p -m 755 "$(dirname "$file")" - printf '%s' "$contents" >"$file" - done - touch -c /lib/systemd/network/*-bmc-gbmcbr.network || true - - contents='[Network]'$'\n' - contents+="Gateway=$rtr"$'\n' - for file in /run/systemd/network/{00,}-bmc-"$NCSI_IF".network.d/49-public-ra.conf; do - mkdir -p -m 755 "$(dirname "$file")" - printf '%s' "$contents" >"$file" - done - touch -c /etc/systemd/network/*-bmc-"$NCSI_IF".network || true - - if [ "$(systemctl is-active systemd-networkd)" != 'inactive' ]; then - networkctl reload && networkctl reconfigure gbmcbr "$NCSI_IF" || true - fi + # Save the IP address for the interface + gbmc_br_set_ip "$pfx" || true # DHCP Relay workaround until alternate source port is supported # TODO: Remove this once internal relaying cleanups land @@ -106,8 +71,6 @@ while true; do (( t_pfx_b[9] |= 1 )) hextet="fd$(printf '%02x' ${t_pfx_b[9]})" pfx="$(ip_bytes_to_str t_pfx_b)" - (( t_pfx_b[9] &= 0xf0 )) - stateless_pfx="$(ip_bytes_to_str t_pfx_b)" elif [[ "$line" =~ ^'DNS search list'' '*:' '*([a-z]+[0-9]+)[^.]*[.](.*.google.com)$ ]]; then host="${BASH_REMATCH[1]}" domain="${BASH_REMATCH[2]}" diff --git a/meta-google/recipes-google/ncsi/gbmc-ncsi-config.bb b/meta-google/recipes-google/ncsi/gbmc-ncsi-config.bb index b761b161af..364afee58e 100644 --- a/meta-google/recipes-google/ncsi/gbmc-ncsi-config.bb +++ b/meta-google/recipes-google/ncsi/gbmc-ncsi-config.bb @@ -21,7 +21,7 @@ SRC_URI += " \ file://gbmc-ncsi-br-pub-addr.sh.in \ file://gbmc-ncsi-br-deprecated-ips.sh.in \ file://gbmc-ncsi-set-nicenabled.service.in \ - file://25-gbmc-ncsi-clear-ip.sh.in \ + file://50-gbmc-ncsi-clear-ip.sh.in \ " S = "${WORKDIR}" @@ -38,7 +38,7 @@ RDEPENDS:${PN} += " \ " FILES:${PN} += " \ - ${datadir}/gbmc-br-dhcp \ + ${datadir}/gbmc-br-lib \ ${datadir}/gbmc-ip-monitor \ ${systemd_unitdir} \ " @@ -108,11 +108,11 @@ do_install:append() { >${WORKDIR}/gbmc-ncsi-br-deprecated-ips.sh install -m644 ${WORKDIR}/gbmc-ncsi-br-deprecated-ips.sh $mondir - dhcpdir=${D}${datadir}/gbmc-br-dhcp/ - install -d -m0755 $dhcpdir - sed "s,@NCSI_IF@,$if_name,g" ${WORKDIR}/25-gbmc-ncsi-clear-ip.sh.in \ - >${WORKDIR}/25-gbmc-ncsi-clear-ip.sh - install -m644 ${WORKDIR}/25-gbmc-ncsi-clear-ip.sh $dhcpdir + brlibdir=${D}${datadir}/gbmc-br-lib/ + install -d -m0755 $brlibdir + sed "s,@NCSI_IF@,$if_name,g" ${WORKDIR}/50-gbmc-ncsi-clear-ip.sh.in \ + >${WORKDIR}/50-gbmc-ncsi-clear-ip.sh + install -m644 ${WORKDIR}/50-gbmc-ncsi-clear-ip.sh $brlibdir sed "s,@NCSI_IF@,$if_name,g" ${WORKDIR}/gbmc-ncsi-set-nicenabled.service.in \ >${D}${systemd_system_unitdir}/gbmc-ncsi-set-nicenabled.service diff --git a/meta-google/recipes-google/networking/gbmc-bridge.bb b/meta-google/recipes-google/networking/gbmc-bridge.bb index 850b14a237..3f2857b874 100644 --- a/meta-google/recipes-google/networking/gbmc-bridge.bb +++ b/meta-google/recipes-google/networking/gbmc-bridge.bb @@ -25,11 +25,14 @@ SRC_URI += " \ file://gbmc-br-dhcp.service \ file://gbmc-br-dhcp-term.sh \ file://gbmc-br-dhcp-term.service \ + file://gbmc-br-lib.sh \ + file://gbmc-br-load-ip.service \ " FILES:${PN}:append = " \ ${datadir}/gbmc-ip-monitor \ ${datadir}/gbmc-br-dhcp \ + ${datadir}/gbmc-br-lib.sh \ ${systemd_unitdir}/network \ ${sysconfdir}/nftables \ ${sysconfdir}/avahi/services \ @@ -48,6 +51,7 @@ SYSTEMD_SERVICE:${PN} += " \ gbmc-br-ensure-ra.service \ gbmc-br-dhcp.service \ gbmc-br-dhcp-term.service \ + gbmc-br-load-ip.service \ " GBMC_BR_MAC_ADDR ?= "" @@ -111,8 +115,11 @@ do_install() { install -m0644 ${WORKDIR}/gbmc-br-ensure-ra.service ${D}${systemd_system_unitdir}/ install -m0644 ${WORKDIR}/gbmc-br-dhcp.service ${D}${systemd_system_unitdir}/ install -m0644 ${WORKDIR}/gbmc-br-dhcp-term.service ${D}${systemd_system_unitdir}/ + install -m0644 ${WORKDIR}/gbmc-br-load-ip.service ${D}${systemd_system_unitdir}/ install -d -m0755 ${D}${datadir}/gbmc-br-dhcp install -m0644 ${WORKDIR}/50-gbmc-psu-hardreset.sh ${D}${datadir}/gbmc-br-dhcp/ + + install -m0644 ${WORKDIR}/gbmc-br-lib.sh ${D}${datadir}/ } do_rm_work:prepend() { diff --git a/meta-google/recipes-google/networking/gbmc-bridge/-bmc-gbmcbr.network.in b/meta-google/recipes-google/networking/gbmc-bridge/-bmc-gbmcbr.network.in index afea5cca79..09ef620351 100644 --- a/meta-google/recipes-google/networking/gbmc-bridge/-bmc-gbmcbr.network.in +++ b/meta-google/recipes-google/networking/gbmc-bridge/-bmc-gbmcbr.network.in @@ -7,5 +7,8 @@ IPv6AcceptRA=true LLMNR=true MulticastDNS=true LinkLocalAddressing=ipv6 +IPv6PrefixDelegation=yes [IPv6AcceptRA] DHCPv6Client=false +[IPv6PrefixDelegation] +RouterLifetimeSec=30 diff --git a/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-dhcp.sh b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-dhcp.sh index 19fa7b1010..9c61036651 100644 --- a/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-dhcp.sh +++ b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-dhcp.sh @@ -15,27 +15,20 @@ # A list of functions which get executed for each bound DHCP lease. # These are configured by the files included below. +# Shellcheck does not understand how this gets referenced +# shellcheck disable=SC2034 GBMC_BR_DHCP_HOOKS=() -# Load configurations from a known location in the filesystem to populate -# hooks that are executed after each event. -shopt -s nullglob -for conf in /usr/share/gbmc-br-dhcp/*.sh; do - # SC doesn't like dynamic source loading - # shellcheck disable=SC1090 - source "$conf" -done - -gbmc_br_dhcp_run_hooks() { - local hook - for hook in "${GBMC_BR_DHCP_HOOKS[@]}"; do - "$hook" || return - done -} - # SC can't find this path during repotest # shellcheck disable=SC1091 source /usr/share/network/lib.sh || exit +# SC can't find this path during repotest +# shellcheck disable=SC1091 +source /usr/share/gbmc-br-lib.sh || exit + +# Load configurations from a known location in the filesystem to populate +# hooks that are executed after each event. +gbmc_br_source_dir /usr/share/gbmc-br-dhcp || exit # Write out the current PID and cleanup when complete trap 'rm -f /run/gbmc-br-dhcp.pid' EXIT @@ -51,57 +44,25 @@ if [ "$1" = bound ]; then # Ensure we are a BMC and have a suffix nibble, the 0th index is reserved if (( pfx_bytes[8] != 0xfd || pfx_bytes[9] & 0xf == 0 )); then echo "Invalid address" >&2 - exit + exit 1 fi # Ensure we don't have more than a /80 address for (( i = 10; i < 16; ++i )); do if (( pfx_bytes[i] != 0 )); then echo "Invalid address" >&2 - exit + exit 1 fi done pfx="$(ip_bytes_to_str pfx_bytes)" - (( pfx_bytes[9] &= 0xf0 )) - stateless_pfx="$(ip_bytes_to_str pfx_bytes)" - read -r -d '' contents <<EOF -[Network] -Address=$pfx/128 -IPv6PrefixDelegation=yes -[IPv6PrefixDelegation] -RouterLifetimeSec=60 -[IPv6Prefix] -Prefix=$stateless_pfx/80 -PreferredLifetimeSec=60 -ValidLifetimeSec=60 -[IPv6RoutePrefix] -Route=$pfx/80 -LifetimeSec=60 -[Route] -Destination=$stateless_pfx/76 -Type=unreachable -Metric=1024 -EOF - - for file in /etc/systemd/network/{00,}-bmc-gbmcbr.network.d/50-public.conf; do - mkdir -p "$(dirname "$file")" - printf '%s' "$contents" >"$file" - done - - # Ensure that systemd-networkd performs a reconfiguration as it doesn't - # currently check the mtime of drop-in files. - touch -c /lib/systemd/network/*-bmc-gbmcbr.network - - if [ "$(systemctl is-active systemd-networkd)" != 'inactive' ]; then - networkctl reload && networkctl reconfigure gbmcbr - fi + gbmc_br_set_ip "$pfx" || exit if [ -n "${fqdn-}" ]; then echo "Using hostname $fqdn" >&2 hostnamectl set-hostname "$fqdn" || true fi - gbmc_br_dhcp_run_hooks || exit + gbmc_br_run_hooks GBMC_BR_DHCP_HOOKS || exit # Ensure that the installer knows we have completed processing DHCP by # running a service that reports completion diff --git a/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-lib.sh b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-lib.sh new file mode 100644 index 0000000000..7ccee8e611 --- /dev/null +++ b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-lib.sh @@ -0,0 +1,133 @@ +#!/bin/bash +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[ -n "${gbmc_br_lib_init-}" ] && return + +# SC can't find this path during repotest +# shellcheck disable=SC1091 +source /usr/share/network/lib.sh || exit + +# A list of functions which get executed for each configured IP. +# These are configured by the files included below. +# Shellcheck does not understand how this gets referenced +# shellcheck disable=SC2034 +GBMC_BR_LIB_SET_IP_HOOKS=() + +gbmc_br_source_dir() { + local dir="$1" + + local file + while read -r -d $'\0' file; do + # SC doesn't like dynamic source loading + # shellcheck disable=SC1090 + source "$file" || return + done < <(shopt -s nullglob; for f in "$dir"/*.sh; do printf '%s\0' "$f"; done) +} + +# Load configurations from a known location in the filesystem to populate +# hooks that are executed after each event. +gbmc_br_source_dir /usr/share/gbmc-br-lib || exit + +gbmc_br_run_hooks() { + local -n hookvar="$1" + shift + local hook + for hook in "${hookvar[@]}"; do + "$hook" "$@" || return + done +} + +gbmc_br_reload() { + if [ "$(systemctl is-active systemd-networkd)" != 'inactive' ]; then + networkctl reload && networkctl reconfigure gbmcbr + fi +} + +gbmc_br_no_ip() { + echo "Runtime removing gbmcbr IP" >&2 + rm -f /run/systemd/network/{00,}-bmc-gbmcbr.network.d/50-public.conf + gbmc_br_reload +} + +gbmc_br_reload_ip() { + local ip="${1-}" + + if [ -z "$ip" ] && ! ip="$(cat /var/google/gbmc-br-ip 2>/dev/null)"; then + echo "Ignoring unconfigured IP" >&2 + gbmc_br_no_ip + return 0 + fi + + local pfx_bytes=() + if ! ip_to_bytes pfx_bytes "$ip"; then + echo "Ignoring Invalid IPv6: $ip" >&2 + gbmc_br_no_ip + return 0 + fi + + local pfx + pfx="$(ip_bytes_to_str pfx_bytes)" + (( pfx_bytes[9] &= 0xf0 )) + local stateless_pfx + stateless_pfx="$(ip_bytes_to_str pfx_bytes)" + local contents + read -r -d '' contents <<EOF +[Network] +Address=$pfx/128 +[IPv6Prefix] +Prefix=$stateless_pfx/80 +PreferredLifetimeSec=60 +ValidLifetimeSec=60 +[IPv6RoutePrefix] +Route=$pfx/80 +LifetimeSec=60 +[Route] +Destination=$stateless_pfx/76 +Type=unreachable +Metric=1024 +EOF + echo "Runtime setting gbmcbr IP: $pfx" >&2 + + local file + for file in /run/systemd/network/{00,}-bmc-gbmcbr.network.d/50-public.conf; do + mkdir -p "$(dirname "$file")" + printf '%s' "$contents" >"$file" + done + + gbmc_br_reload +} + +gbmc_br_set_ip() { + local ip="${1-}" + + if [ -n "$ip" ]; then + mkdir -p /var/google || return + echo "$ip" >/var/google/gbmc-br-ip || return + else + rm -rf /var/google/gbmc-br-ip + fi + + # Remove legacy network configuration + rm -rf /etc/systemd/network/{00,}-bmc-gbmcbr.network.d + + gbmc_br_run_hooks GBMC_BR_LIB_SET_IP_HOOKS "$ip" || return + + gbmc_br_reload_ip "$ip" +} + +gbmc_br_lib_init=1 +return 0 2>/dev/null +echo "gbmc-br-lib is a library, not executed directly" >&2 +exit 1 diff --git a/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-load-ip.service b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-load-ip.service new file mode 100644 index 0000000000..51c68eb175 --- /dev/null +++ b/meta-google/recipes-google/networking/gbmc-bridge/gbmc-br-load-ip.service @@ -0,0 +1,9 @@ +[Unit] +Before=gbmc-ip-monitor.service +Before=systemd-networkd.service + +[Service] +ExecStart=/bin/bash -c 'source /usr/share/gbmc-br-lib.sh && gbmc_br_reload_ip' + +[Install] +WantedBy=multi-user.target |