From: Ian Kelling Date: Sat, 14 Feb 2026 10:04:15 +0000 (-0500) Subject: make create idempotent, add mount feature X-Git-Url: https://iankelling.org/git/?a=commitdiff_plain;h=e7eb7d4dc7152c87a777e49b2c049e2f034a37c7;p=newns make create idempotent, add mount feature --- diff --git a/newns b/newns index 9a7a6ff..178447c 100755 --- a/newns +++ b/newns @@ -39,9 +39,7 @@ m() { usage() { cat <. EOF @@ -123,11 +104,17 @@ EOF #### begin arg parsing #### create=false -temp=$(getopt -l help,create hcdn: "$@") || usage 1 +declare -a bind_srcs bind_dests +temp=$(getopt -l help,create hcdm:n: "$@") || usage 1 eval set -- "$temp" while true; do case $1 in -c|--create) create=true; shift ;; + -m) + bind_srcs+=( "${2#:*}" ) + bind_dests+=( "${2%:*}" ) + shift 2 + ;; -n) network=$2; shift 2 ;; -h|--help) usage ;; --) shift; break ;; @@ -233,50 +220,60 @@ ip-add() { cmd=$1 net=$2 dev=$3 - if ! $cmd addr show dev $dev | sed 's/^ *//;s/ *$//' | grep -xF "inet $net scope global $dev"; then + if ! $cmd addr show dev $dev | sed 's/^ *//;s/ *$//' | grep -xFq "inet $net scope global $dev"; then $cmd addr add $net dev $dev fi } +check-default-route() { + default_route_done=false + default_route=$(ipnn route show default | sed -r 's,^[[:space:]]+|[[:space:]]+$,,') + if [[ $default_route == "default via $network.1 dev $v1" ]]; then + default_route_done=true + fi +} + start() { find-network #### begin mount namespace setup #### - mkdir -p /run/mount-namespaces - if ! mountpoint /run/mount-namespaces >/dev/null; then - mount --bind /run/mount-namespaces /run/mount-namespaces - fi - # note: This is outside the mount condition because I've mysteriously - # had this become shared instead of private, perhaps it - # got remounted somehow and lost the setting. - mount --make-private /run/mount-namespaces - if [[ ! -e /run/mount-namespaces/$nn ]]; then - touch /run/mount-namespaces/$nn - fi - if ! mountpoint /run/mount-namespaces/$nn >/dev/null; then - # Here, we specify that we only want mount changes changes under - # this mountpoint to be propagated into the bind, but changes - # from within the bind do not propagate to outside the bind. - # - # slave is documented in. - # /usr/share/doc/linux-doc-4.9/Documentation/filesystems/sharedsubtree.txt.gz - # documentation on propagation is a bit weird because it - # confusingly talks about binds, namespaces, and mirrors (which - # seems to be just another name for bind), shared subtrees - # (which seems to be a term for binds and namespaces), and does not - # properly specify whether the documentation applies to binds, - # namespaces, or both. Notably, propagation for binds is marked - # on the original mount point, and propagation for a mount - # namespace is marked on mounts within the namespace. - unshare --propagation slave --mount=/run/mount-namespaces/$nn /bin/true + if (( ${#bind_srcs[@]} )); then + mkdir -p /run/mount-namespaces + if ! mountpoint /run/mount-namespaces >/dev/null; then + mount --bind /run/mount-namespaces /run/mount-namespaces + fi + # note: This is outside the mount condition because I've mysteriously + # had this become shared instead of private, perhaps it + # got remounted somehow and lost the setting. + mount --make-private /run/mount-namespaces + if [[ ! -e /run/mount-namespaces/$nn ]]; then + touch /run/mount-namespaces/$nn + fi + if ! mountpoint /run/mount-namespaces/$nn >/dev/null; then + # Here, we specify that we only want mount changes changes under + # this mountpoint to be propagated into the bind, but changes + # from within the bind do not propagate to outside the bind. + # + # slave is documented in. + # /usr/share/doc/linux-doc-4.9/Documentation/filesystems/sharedsubtree.txt.gz + # documentation on propagation is a bit weird because it + # confusingly talks about binds, namespaces, and mirrors (which + # seems to be just another name for bind), shared subtrees + # (which seems to be a term for binds and namespaces), and does not + # properly specify whether the documentation applies to binds, + # namespaces, or both. Notably, propagation for binds is marked + # on the original mount point, and propagation for a mount + # namespace is marked on mounts within the namespace. + unshare --propagation slave --mount=/run/mount-namespaces/$nn /bin/true + fi fi #### end mount namespace setup #### if $create; then - if ! ip netns | grep -xF $nn &>/dev/null; then + if ! ip netns | awk '{print $1}' | grep -xF $nn &>/dev/null; then ip netns add $nn fi ip -n $nn link set dev lo up @@ -290,76 +287,43 @@ start() { err-cleanup() { stop; } - ipnn link add $v0 type veth peer name $v1 - ipnn link set $v0 netns default + if ! ip link show $v0 &>/dev/null; then + ipnn link add $v0 type veth peer name $v1 + ipnn link set $v0 netns default + fi ip-add ipd $network.1/24 $v0 ipd link set $v0 up nat -C &>/dev/null || nat -A ip-add ipnn $network.2/24 $v1 ipnn link set $v1 up cmd="ipnn route add default via $network.1" - $cmd - fails=0 - max_fails=2 + tries=0 + max_tries=3 # I've had adding the default route mysteriously fail on boot, so # here we check that it succeeded, do a sleep and a retry. while true; do - default_route=$(ipnn route show default | sed -r 's,^[[:space:]]+|[[:space:]]+$,,') - if [[ $default_route != "default via $network.1 dev $v1" ]]; then - fails=$((fails + 1)) - else + check-default-route + if $default_route_done; then break + else + $cmd + tries=$((tries + 1)) fi - if (( fails >= max_fails )); then - echo "$0: ERROR: default route added but not found, retried $max_fails. expected route: 'default via $network.1 dev $v1', found: '$default_route'" - # Note: for debugging, if you have a systemd unit which tears down - # the newns upon failure, you may want to uncomment the break so - # that we proceed and can inspect the system. break + if (( tries >= max_tries )); then + echo "$0: ERROR: default route added but not found, tried $max_tries. expected route: 'default via $network.1 dev $v1', found: '$default_route'" exit 1 else sleep 1 $cmd fi done - if (( fails >= 1 )); then - echo "$0: WARNING: route added but not found until retried $max_fails times: $cmd" + if (( tries >= 2 )); then + echo "$0: WARNING: route added but not found until retried $max_tries times: $cmd" fi - - ###### begin setup resolvconf - if [[ -e /run/resolvconf ]]; then # resolvconf probably installed - resolv_copy=/root/resolvconf-$nn - - # this condition should never happen, just coding defensively - if mexec mountpoint /run/resolvconf &>/dev/null; then - mexec umount /run/resolvconf - fi - cp -aT /run/resolvconf $resolv_copy - if ! mexec mount -o bind $resolv_copy /run/resolvconf; then - echo "error: resolv-conf bindmount failed" - exit 1 - fi - # if running dnsmasq, we have 127.0.0.1 for dns, but it can't listen on the loopback - # in the network namespace, so adjust the address. - if mexec [ -s /run/resolvconf/interface/lo.dnsmasq ]; then - mexec sed --follow-symlinks -i "s/nameserver 127\..*/nameserver $network.1/" /run/resolvconf/interface/lo.dnsmasq - mexec resolvconf -u - fi - # and in debian based distros at least, it runs with --local-service, and needs a restart - # to know about the new local network - if [[ $(systemctl --no-pager show -p ActiveState dnsmasq ) == ActiveState=active ]]; then - systemctl restart dnsmasq - fi - - # background: if we did this in openvpn's resolv-conf script, we could guard it in - # if capsh --print|grep '\bcap_sys_admin\b' &>/dev/null - # and we could get $nn by - # config_basename=${config%%.*} - # config_basename=${config_basename##*/} - # but dnsmasq forces us to do it earlier. - - fi # end if [[ -e /run/resolvconf ]] - ###### end setup resolvconf + for ((i=0; i < ${#bind_srcs[@]}; i++ )); do + mexec mount -o bind ${bind_srcs[$i]} ${bind_dests[$i]} + done } stop() { @@ -376,11 +340,7 @@ stop() { ip netns del $nn fi - # not sure this is necessary since we are tearing down the mount namespace - if mexec mountpoint /run/resolvconf &>/dev/null; then - mexec umount /run/resolvconf - fi - + # todo: do we need to umount the bind mounts within the mount namespace first? if mountpoint /run/mount-namespaces/$nn >/dev/null; then umount /run/mount-namespaces/$nn fi @@ -391,7 +351,7 @@ show() { m dexec iptables -t nat -C POSTROUTING -s $network.0/24 -j MASQUERADE \ -m comment --comment "systemd network namespace nat" ||: m dexec iptables -C FORWARD -i $v0 -j ACCEPT - m mexec mountpoint /run/resolvconf + m mexec mount m mountpoint /run/mount-namespaces/$nn }