a2233fb700b077a6933381f812ed9b5ce89919c7
[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 NS_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 a systemd network namespace, nat it to the outside. This
47 would be called from ExecStartPre, and or subsequent units called with
48 JoinsNamespaceOf= and PrivateNetwork=true.
49
50 If given -c, or if in the default network namespace, create a named
51 network namepace natted to the current netns.
52
53 Uses /24 network, finding the first locally unused one starting at
54 10.173.0.
55
56 Also create a named mount namespace under /root/mount_namespaces, so we
57 can alter some system config for this namespace. Subsequent systemd
58 command lines would be prefixed with:
59
60 /usr/bin/nsenter --mount=/root/mount_namespaces/NS_NAME
61
62 Note, this means that they can't run as unpriveledged users, but once
63 systemd 233 comes out, it will have a bind mount option from within unit
64 files, so the mount namespace won't be needed for most use cases, and I
65 will update the script to that the mount namespace not created unless a
66 flag is passed in. Patch welcome to add that flag before then.
67
68 A recommmended dependency of this script is my other repo named "errhandle",
69 which prints stack trace on error, and calls a cleanup function:
70 https://iankelling.org/git/?p=errhandle, set ERRHANDLE_PATH, or put it
71 in a directory adjacent to the absolute, resolved directory this file is
72 in.
73
74 Background:
75
76 This script does not make the namespace be named like ip does, because
77 the naming is not necessary, although it could have with some more
78 work. For debugging and joining the namespace with a bash shell, I use
79 nsenter -n -m -t $(pgrep PROCESS_IN_NAMESPACE). Note: if I knew how to
80 easily ask systemd what pid a unit has, i would do that.
81
82 "ip netns new ..." also does a mount namespace, then bind
83 mounts each file/dir in /etc/netns/NS_NAME to /etc/NS_NAME. Note,
84 for openvpn having it's own resolv.conf by using it's user script which
85 calls resolvconf, this doesn't help much. What we actually want to do is
86 copy /run/resolvconf somehwere then bind mount it on top of
87 /run/resolvconf.
88
89 Please email me if you have a patches, bugs, feedback, or republish this
90 somewhere else: Ian Kelling <ian@iankelling.org>.
91 EOF
92 exit ${1:-0}
93 }
94
95
96 #### begin arg parsing ####
97 create=false
98 temp=$(getopt -l help,create hc "$@") || usage 1
99 eval set -- "$temp"
100 while true; do
101 case $1 in
102 -c|--create) create=true; shift ;;
103 -h|--help) usage ;;
104 --) shift; break ;;
105 *) echo "$0: Internal error!" ; exit 1 ;;
106 esac
107 done
108 if (( $# != 2 )); then
109 usage 1
110 fi
111
112 action=$1
113 nn=$2 # namespace name
114 #### end arg parsing ####
115
116 #### begin sanity checking ####
117 install_error=false
118 if ! type -p ip &>/dev/null; then
119 echo "please install the iproute2 package"
120 install_error=true
121 fi
122 if ! type -p iptables &>/dev/null; then
123 echo "please install the iptables package"
124 install_error=true
125 fi
126 if $install_error; then
127 exit 1
128 fi
129 #### end sanity checking ####
130
131
132 v0=veth0-$nn
133 v1=veth1-$nn
134 ip_base=10.173
135
136 if ! $create && [[ $(readlink /proc/self/ns/net) == "$(readlink /proc/1/ns/net)" ]]; then
137 create=true
138 fi
139
140 # make the default network namespace be named
141 target=/run/netns/default
142 if [[ ! -e $target && ! -L $target ]]; then
143 mkdir -p /run/netns
144 ln -s /proc/1/ns/net $target
145 fi
146
147
148 ipd() { ip -n default "$@"; }
149 if $create; then
150 ipnn() { ip -n $nn "$@"; }
151 else
152 # we are already in the network namespace and it's unnamed.
153 ipnn() { ip "$@"; }
154 fi
155 dexec() { ip netns exec default "$@"; }
156
157
158 # background: head -n1 is defensive. Not sure if there is some weird feature
159 # for 2 routes to be 0/0.
160 gateway_if=$(ipd route list exact 0/0 | head -n1| sed -r 's/.*\s(\S+)\s*$/\1/')
161 nat() { dexec iptables -t nat $1 POSTROUTING -o $gateway_if -j MASQUERADE \
162 -m comment --comment "systemd network namespace nat"; }
163
164 find_network() {
165 found=false
166 existing=false
167 ips="$(ipd addr show | awk '$1 == "inet" {print $2}')"
168 for ((i=0; i <= 254; i++)); do
169 network=$ip_base.$i
170 if printf "%s\n" "$ips" | grep "^${network//./\\.}" >/dev/null; then
171 existing=true
172 else
173 found=true
174 break
175 fi
176 done
177 }
178
179 start() {
180 find_network
181 if ! $found; then
182 echo "$0: error: no open network found"
183 exit 1
184 fi
185
186 #### begin mount namespace setup ####
187 mkdir -p /root/mount_namespaces
188 if ! mountpoint /root/mount_namespaces >/dev/null; then
189 mount --bind /root/mount_namespaces /root/mount_namespaces
190 mount --make-private /root/mount_namespaces
191 fi
192 if [[ ! -e /root/mount_namespaces/$nn ]]; then
193 touch /root/mount_namespaces/$nn
194 fi
195 if ! mountpoint /root/mount_namespaces/$nn >/dev/null; then
196 unshare --mount=/root/mount_namespaces/$nn
197 fi
198 #### end mount namespace setup ####
199
200
201 if $create; then
202 ip netns add $nn
203 ip -n $nn link set dev lo up
204 fi
205
206 echo 1 | dexec dd of=/proc/sys/net/ipv4/ip_forward 2>/dev/null
207
208 _errcatch_cleanup=stop
209 ipnn link add $v0 type veth peer name $v1
210 ipnn link set $v0 netns default
211 ipd addr add $network.1/24 dev $v0
212 ipd link set $v0 up
213 nat -C &>/dev/null || nat -A
214 ipnn addr add $network.2/24 dev $v1
215 ipnn link set $v1 up
216 ipnn route add default via $network.1
217
218 }
219
220 stop() {
221 if ipd link list $v0 &>/dev/null; then
222 # this also deletes $v1 and the route we added.
223 ipd link del $v0
224 fi
225 find_network
226 if ! $existing; then
227 if nat -C &>/dev/null; then nat -D; fi
228 fi
229 if $create; then
230 ip netns del $nn
231 fi
232 if mountpoint /root/mount_namespaces/$nn >/dev/null; then
233 umount /root/mount_namespaces/$nn
234 fi
235 }
236
237 case $action in
238 start|stop)
239 $action
240 ;;
241 *)
242 echo "$0: error: unsupported action"
243 exit 1
244 ;;
245 esac