2 # Copyright (C) 2017 Ian Kelling
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 [[ $EUID == 0 ]] ||
exec sudo
-E "$BASH_SOURCE" "$@"
19 if [[ ! $ERRHANDLE_PATH ]]; then
20 ERRHANDLE_PATH
=$
(readlink
-f "${BASH_SOURCE}")
21 ERRHANDLE_PATH
=$
(readlink
-f ${ERRHANDLE_PATH%/*}/..
/errhandle
)
24 for p
in $ERRHANDLE_PATH/{errcatch-function
,bash-trace-function
}; do
35 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
40 usage: ${0##*/} [OPTS] start|stop NS_NAME
41 Setup & optionally create, a network namespace with nat and a mount namespace
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
47 -h, --help Show this help and exit.
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.
53 Uses /24 network, finding the first locally unused one starting at
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:
60 /usr/bin/nsenter --mount=/root/mount_namespaces/NS_NAME
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.
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
76 This script does not make the namespace be named like ip does, because
77 the naming is not necessary, although it could have been done with some
78 more work. For debugging and joining the namespace with a bash shell, I
79 use nsenter -n -m -t $(pgrep PROCESS_IN_NAMESPACE) bash. Note: if I
80 knew how to easily ask systemd what pid a unit has, i would do that.
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
89 Please email me if you have a patches, bugs, feedback, or republish this
90 somewhere else: Ian Kelling <ian@iankelling.org>.
96 #### begin arg parsing ####
98 temp
=$
(getopt
-l help,create hc
"$@") || usage
1
102 -c|
--create) create
=true
; shift ;;
105 *) echo "$0: Internal error!" ; exit 1 ;;
108 if (( $# != 2 )); then
113 nn
=$2 # namespace name
114 #### end arg parsing ####
116 #### begin sanity checking ####
118 if ! type -p ip
&>/dev
/null
; then
119 echo "please install the iproute2 package"
122 if ! type -p iptables
&>/dev
/null
; then
123 echo "please install the iptables package"
126 if $install_error; then
129 #### end sanity checking ####
136 if ! $create && [[ $
(readlink
/proc
/self
/ns
/net
) == "$(readlink /proc/1/ns/net)" ]]; then
140 # make the default network namespace be named
141 target
=/run
/netns
/default
142 if [[ ! -e $target && ! -L $target ]]; then
144 ln -s /proc
/1/ns
/net
$target
148 ipd
() { ip
-n default
"$@"; }
150 ipnn
() { ip
-n $nn "$@"; }
152 # we are already in the network namespace and it's unnamed.
155 dexec
() { ip netns
exec default
"$@"; }
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"; }
167 ips
="$(ipd addr show | awk '$1 == "inet
" {print $2}')"
168 for ((i
=0; i
<= 254; i
++)); do
170 if printf "%s\n" "$ips" |
grep "^${network//./\\.}" >/dev
/null
; then
182 echo "$0: error: no open network found"
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
192 if [[ ! -e /root
/mount_namespaces
/$nn ]]; then
193 touch /root
/mount_namespaces
/$nn
195 if ! mountpoint
/root
/mount_namespaces
/$nn >/dev
/null
; then
196 unshare
--mount=/root
/mount_namespaces
/$nn
198 #### end mount namespace setup ####
203 ip
-n $nn link
set dev lo up
206 echo 1 | dexec
dd of
=/proc
/sys
/net
/ipv
4/ip_forward
2>/dev
/null
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
213 nat
-C &>/dev
/null || nat
-A
214 ipnn addr add
$network.2/24 dev
$v1
216 ipnn route add default via
$network.1
221 if ipd link list
$v0 &>/dev
/null
; then
222 # this also deletes $v1 and the route we added.
227 if nat
-C &>/dev
/null
; then nat
-D; fi
232 if mountpoint
/root
/mount_namespaces
/$nn >/dev
/null
; then
233 umount
/root
/mount_namespaces
/$nn
242 echo "$0: error: unsupported action"