delete the mount namespace on stop
[newns] / newns
1 #!/bin/bash
2 # Copyright (C) 2017 Ian Kelling
3
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7
8 # http://www.apache.org/licenses/LICENSE-2.0
9
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16
17 [[ $EUID == 0 ]] || exec sudo -E "$BASH_SOURCE" "$@"
18
19 if [[ ! $ERRHANDLE_PATH ]]; then
20 ERRHANDLE_PATH=$(readlink -f "${BASH_SOURCE}")
21 ERRHANDLE_PATH=$(readlink -f ${ERRHANDLE_PATH%/*}/../errhandle)
22 fi
23 err_sourced=true
24 for p in $ERRHANDLE_PATH/{errcatch-function,bash-trace-function}; do
25 if [[ -e $p ]]; then
26 source $p
27 else
28 err_sourced=false
29 fi
30 done
31 if $err_sourced; then
32 errcatch
33 else
34 set -eE -o pipefail
35 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
36 fi
37
38 usage() {
39 cat <<EOF
40 usage: ${0##*/} [OPTS] start|stop NETNS_NAME
41 Setup new or systemd created network namespace with nat and mount namespace
42
43 -c, --create Create network namespace. For running outside systemd private net.
44 -h, --help Show this help and exit.
45
46 From within systemd network namespace, nat it to the outside. If given
47 -c, or if in the default network namespace, create a named network
48 namepace natted to the current netns.
49
50 Also create a named mount namespace under /root/mount_namespaces, so we
51 can alter some system config for this namespace. Subsequent systemd
52 command lines would be prefixed with:
53
54 /usr/bin/nsenter --mount=/root/mount_namespaces/NETNS_NAME
55
56
57 "ip netns new ..." also does a mount namespace, then bind mounts each
58 thing in /etc/netns/NETNS_NAME to /etc/NETNS_NAME. Note, for openvpn having it's own
59 resolv.conf, this doesn't help much. What we actually want to do is copy
60 /run/resolvconf somehwere, then bind mount it on top of /run/resolvconf.
61
62 Once systemd 233 comes out, it will have a bind mount option from within
63 unit files, so the mount namespace won't be needed for this use case.
64
65 Recommmended dependency of errhandle to print stack trace on error:
66 https://iankelling.org/git/?p=errhandle, set ERRHANDLE_PATH, or put it
67 in a directory adjacent to the absolute, resolved directory this file is
68 in.
69
70 EOF
71 exit ${1:-0}
72 }
73
74
75 ## begin arg parsing ##
76 create=false
77 temp=$(getopt -l help,create hc "$@") || usage 1
78 eval set -- "$temp"
79 while true; do
80 case $1 in
81 -c|--create) create=true; shift ;;
82 -h|--help) usage ;;
83 --) shift; break ;;
84 *) echo "$0: Internal error!" ; exit 1 ;;
85 esac
86 done
87 if (( $# != 2 )); then
88 usage 1
89 fi
90
91 action=$1
92 nn=$2 # network namespace / namespace name
93 ## end arg parsing ##
94
95 ## begin sanity checking ##
96
97 install_error=false
98 if ! type -p ip &>/dev/null; then
99 echo "please install the iproute2 package"
100 install_error=true
101 fi
102 if ! type -p iptables &>/dev/null; then
103 echo "please install the iptables package"
104 install_error=true
105 fi
106 if $install_error; then
107 exit 1
108 fi
109
110 ## end sanity checking ##
111
112
113 v0=veth0-$nn
114 v1=veth1-$nn
115 ip_base=10.173
116
117 if ! $create && [[ $(readlink /proc/self/ns/net) == "$(readlink /proc/1/ns/net)" ]]; then
118 create=true
119 fi
120
121 target=/run/netns/default
122 if [[ ! -e $target && ! -L $target ]]; then
123 mkdir -p /run/netns
124 # make the default network namespace be named
125 ln -s /proc/1/ns/net $target
126 fi
127
128
129 ipd() { ip -n default "$@"; }
130 if $create; then
131 ipnn() { ip -n $nn "$@"; }
132 else
133 # we are already in the network namespace and it's unnamed.
134 ipnn() { ip "$@"; }
135 fi
136 dexec() { ip netns exec default "$@"; }
137
138
139 # head -n1 is defensive. Not sure if there is some weird feature
140 # for 2 routes to be 0/0.
141 gateway_if=$(ipd route list exact 0/0 | head -n1| sed -r 's/.*\s(\S+)\s*$/\1/')
142 nat() { dexec iptables -t nat $1 POSTROUTING -o $gateway_if -j MASQUERADE \
143 -m comment --comment "systemd network namespace nat"; }
144
145 find_network() {
146 found=false
147 existing=false
148 ips="$(ipd addr show | awk '$1 == "inet" {print $2}')"
149 for ((i=0; i <= 254; i++)); do
150 network=$ip_base.$i
151 if printf "%s\n" "$ips" | grep "^${network//./\\.}" >/dev/null; then
152 existing=true
153 else
154 found=true
155 break
156 fi
157 done
158 }
159
160 start() {
161
162 find_network
163 if ! $found; then
164 echo "$0: error: no open network found"
165 exit 1
166 fi
167
168 mkdir -p /root/mount_namespaces
169 if ! mountpoint /root/mount_namespaces >/dev/null; then
170 mount --bind /root/mount_namespaces /root/mount_namespaces
171 mount --make-private /root/mount_namespaces
172 fi
173 if [[ ! -e /root/mount_namespaces/$nn ]]; then
174 touch /root/mount_namespaces/$nn
175 fi
176 if ! mountpoint /root/mount_namespaces/$nn >/dev/null; then
177 unshare --mount=/root/mount_namespaces/$nn
178 fi
179
180
181 if $create; then
182 ip netns add $nn
183 ip -n $nn link set dev lo up
184 fi
185
186
187
188 echo 1 | dexec dd of=/proc/sys/net/ipv4/ip_forward 2>/dev/null
189
190 _errcatch_cleanup=stop
191 ipnn link add $v0 type veth peer name $v1
192 ipnn link set $v0 netns default
193 ipd addr add $network.1/24 dev $v0
194 ipd link set $v0 up
195 nat -C &>/dev/null || nat -A
196 ipnn addr add $network.2/24 dev $v1
197 ipnn link set $v1 up
198 ipnn route add default via $network.1
199
200 }
201
202 stop() {
203 if ipd link list $v0 &>/dev/null; then
204 # this also deletes $v1 and the route we added.
205 ipd link del $v0
206 fi
207 find_network
208 if ! $existing; then
209 if nat -C &>/dev/null; then nat -D; fi
210 fi
211 if $create; then
212 ip netns del $nn
213 fi
214 if mountpoint /root/mount_namespaces/$nn >/dev/null; then
215 umount /root/mount_namespaces/$nn
216 fi
217 }
218
219 case $action in
220 start|stop)
221 $action
222 ;;
223 *)
224 echo "$0: error: unsupported action"
225 exit 1
226 ;;
227 esac