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