ns - scripts - various script and utils
HTML git clone git://z3bra.org/scripts
DIR Log
DIR Files
DIR Refs
---
ns (7136B)
---
1 #!/bin/sh
2
3 set -e
4
5 verbose=0
6 create=0
7 detach=0
8 nsdir=${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/ns
9 nstmpl="ns-XXXX"
10 nsuser="wgs"
11 nsnet="172.16.0.0/12"
12
13 usage() {
14 echo "usage: $(basename $0) [-vclk] [-n name] [cmd [arg…]]" >&2
15 }
16
17 logset() {
18 [ "${verbose:-0}" -gt 0 ] && printf '[ ] %s ' "$*" >&2 || true
19 }
20
21 logchk() {
22 [ "${verbose:-0}" -gt 0 ] && printf '\r[[1;32mx[0m]\n' >&2 || true
23 }
24
25 logdie() {
26 [ "${verbose:-0}" -gt 0 ] && printf '\r[[1;31m![0m]\n' >&2
27 ns_cleanup $nsname
28 exit 1
29 }
30
31 nslist() {
32 [ -d /run/netns ] && ls -1 /run/netns
33 }
34
35 nspid() { # 2nd PID is unshare(1)'s first child, aka PID 1
36 ip netns pids $nsname|head -n2|tail -n1
37 }
38
39 nscwd() {
40 readlink -f /proc/$(nspid)/cwd
41 }
42
43 nsip() {
44 ns_exec ip -4 -o addr show "if"|awk '{print $4}'
45 }
46
47 ns_cleanup() {
48 nsname="$1"
49 if=$(ip -o link show|awk "/netns $nsname/{print \$2}"|cut -d@ -f1)
50 if [ -n "$if" ]; then
51 net=$(ip -o -4 addr show dev "$if"|awk '{print $4}')
52 doas iptables -t nat -D POSTROUTING -s "$net" -j MASQUERADE
53 fi
54 ip netns pids $nsname | xargs -r kill -9
55 doas ip netns del $nsname
56 doas umount -Rq $nsdir/$nsname
57 rmdir "$nsdir/$nsname"
58 }
59
60 ns_mkfs() {
61 dir=$1
62 logset "Create rootfs structure"
63
64 mkdir -p $dir
65 doas mount -t tmpfs tmpfs "$dir"
66 for d in bin etc lib home/$nsuser tmp mnt dev dev/pts dev/shm sys proc; do
67 mkdir -p $dir/$d
68 done
69 for d in null zero full random urandom tty; do
70 touch "$dir/dev/$d"
71 doas mount --bind "/dev/$d" "$dir/dev/$d"
72 done
73 logchk
74
75 logset "Generate system files"
76 cp -r /usr/share/terminfo $dir/etc/
77 install -D /etc/ssl/certs/ca-certificates.crt $dir/etc/ssl/certs/ca-certificates.crt
78 echo "$nsname" > $dir/etc/hostname
79 echo "hosts: files dns" > $dir/etc/nsswitch.conf
80 echo "127.0.0.1 localhost" > $dir/etc/hosts
81 printf 'nameserver %s\n' $(resolvectl dns|cut -d: -f2)|sort|uniq > $dir/etc/resolv.conf
82 printf "export PS1='(%s) $ '" $nsname > $dir/etc/profile
83 printf "%s:x:0:0::/home/%s:/bin/sh" $nsuser $nsuser > $dir/etc/passwd
84 printf "%s:*::0:::::" $nsuser > $dir/etc/shadow
85 printf "world:x:0:%s\n" $nsuser > $dir/etc/group
86 ln -s ../proc/mounts "$dir/etc/mtab"
87 ln -sf pts/ptmx "$dir/dev/ptmx"
88 ln -sf / "$dir/usr"
89 logchk
90
91 cat <<-EOF > "$dir/etc/rc"
92 #!/bin/sh
93 /bin/mount -t sysfs sys /sys
94 /bin/mount -t tmpfs tmpfs /dev/shm
95 /bin/mount -t devpts devpts /dev/pts -o newinstance,ptmxmode=0666
96 /bin/hostname $nsname
97 EOF
98 chmod +x $dir/etc/rc
99
100 logset "Compile /bin/init"
101 cc -x c - -o $dir/bin/init <<-EOF
102 #include <signal.h>
103 #include <unistd.h>
104 #include <sys/wait.h>
105 int main() {
106 sigset_t set; int signum;
107 if (getpid()!=1) return 1;
108 sigfillset(&set); sigprocmask(SIG_BLOCK, &set, 0);
109 if (fork()>0) while(!sigwait(&set, &signum)) wait(NULL);
110 sigprocmask(SIG_UNBLOCK, &set, 0); setsid(); setpgid(0, 0);
111 return execve("/etc/rc", (char *[]){"rc", 0}, (char *[]){0});
112 }
113 EOF
114 logchk
115 }
116
117 ns_install() {
118 dir=$1
119 logset "Boostrap utilities"
120 # https://pubs.opengroup.org/onlinepubs/9799919799/idx/utilities.html
121 exe="admin alias ar asa at awk basename batch bc bg c17 cal"
122 exe="$exe cat cd cflow chgrp chmod chown cksum cmp comm command"
123 exe="$exe compress cp crontab csplit ctags cut cxref date dd"
124 exe="$exe delta df diff dirname du echo ed env ex expand expr"
125 exe="$exe false fc fg file find fold fuser gencat get getconf"
126 exe="$exe getopts gettext grep hash head iconv id ipcrm ipcs"
127 exe="$exe jobs join kill lex link ln locale localedef logger"
128 exe="$exe logname lp ls m4 mailx make man mesg mkdir mkfifo more"
129 exe="$exe msgfmt mv newgrp ngettext nice nl nm nohup od paste"
130 exe="$exe patch pathchk pax pr printf prs ps pwd read readlink"
131 exe="$exe realpath renice rm rmdel rmdir sact sccs sed sh sleep"
132 exe="$exe sort split strings strip stty tabs tail talk tee test"
133 exe="$exe time timeout touch tput tr true tsort tty type ulimit"
134 exe="$exe umask unalias uname uncompress unexpand unget uniq"
135 exe="$exe unlink uucp uudecode uuencode uustat uux val vi wait"
136 exe="$exe wc what who write xargs xgettext yacc zcat"
137
138 # Missing from the above list
139 exe="$exe mksh bash busybox mount umount mountpoint"
140 exe="$exe tar gzip bzip2 xz zstd less"
141
142 # Network utils
143 exe="$exe hostname ip ping host dig curl ssh tcpdump"
144
145 which $exe|xargs cp -t $dir/bin
146 ldd $(readlink -f `which $exe`) 2>/dev/null \
147 |awk '/=>/{print $3}/^\s+\//{print $1}' \
148 |sed 's,^/usr,,'|sort|uniq|cpio --quiet -updL $dir
149 logchk
150 }
151
152 ns_boot() {
153 ns=$1
154 logset "Start init process"
155 unshare -rf -Uuimpn --mount-proc --kill-child -R $ns -- /bin/init &
156 doas ip netns attach $nsname $!
157
158 [ -z "$(nspid)" ] && logdie "Init process failed to start"
159 logchk
160 }
161
162 ns_network() {
163 ns=$1
164 pid=$(nspid)
165 nsif="$nsname"
166 logset "Setup network"
167
168 # Grab a random prefix from the given netblock to handle
169 # communication between host and namespace (through NAT)
170 pfx=20
171 net=$(ipcalc -nb "$nsnet" $pfx|awk '/Network/{print $2}'|shuf|head -n1)
172 min=$(ipcalc -nb $net|awk '/HostMin/{print $2}')
173 max=$(ipcalc -nb $net|awk '/HostMax/{print $2}')
174 doas ip link add $nsif type veth peer ${nsif}-peer
175 doas ip link set $nsif up
176 doas ip addr add "$min/$pfx" dev $nsif
177 doas sysctl -qw net.ipv4.ip_forward=1
178 doas iptables -t nat -A POSTROUTING -s $net -j MASQUERADE
179
180 # Move 2nd pair of veth inside the namespace
181 doas ip link set ${nsif}-peer netns $nsname
182 nsenter -t "$(nspid)" -r --wd=$ns --preserve-credentials -U -u -p -i -n -m -- env -i /bin/sh <<-EOF
183 /bin/ip link set lo up
184 /bin/ip link set ${nsif}-peer name if
185 /bin/ip link set if up
186 /bin/ip address add ${max}/${pfx} dev if
187 /bin/ip route add default via ${min}
188 EOF
189 logchk
190 }
191
192 ns_pop() {
193 # Generate random nsname if not provided
194 : ${nsname:=ns-$(tr -cd a-z </dev/urandom|dd status=none bs=1 count=5)}
195
196 mkdir -p "$nsdir/$nsname"
197 trap "ns_cleanup $nsname" EXIT INT TERM QUIT
198
199 ns_mkfs "$nsdir/$nsname"
200 ns_install "$nsdir/$nsname"
201 ns_boot "$nsdir/$nsname"
202 ns_network "$nsdir/$nsname"
203 trap - EXIT
204 }
205
206 ns_exec() {
207 pid=$(nspid)
208 cwd=$(nscwd)
209 nshome=$(head -n1 $cwd/etc/passwd|cut -d: -f 6)
210 nsenter -t $pid -r --wd=$cwd --preserve-credentials -U -u -p -i -n -m -- \
211 env -i -C $nshome \
212 PATH=/bin \
213 USER=$nsuser \
214 HOME=$nshome \
215 TERM="${TERM:-vt100}" \
216 LESS="${LESS:-R}" \
217 PAGER="${PAGER:-less}" \
218 EDITOR="${EDITOR:-vi}" \
219 VISUAL="${VISUAL:-vi}" \
220 "$@"
221 }
222
223 ns_info() {
224 nsname="$1"
225 [ -z "$(nspid)" ] && exit 1
226 cat <<-EOF
227 name: $nsname
228 user: $(cut -d: -f1 < $(nscwd)/etc/passwd)
229 root: $(nscwd)
230 init: $(nspid)
231 ipv4: $(nsip)
232 EOF
233 }
234
235 while getopts "hvcdli:k:n:u:" OPT; do
236 case $OPT in
237 d) detach=1;;
238 n) nsname="$OPTARG";;
239 u) nsuser="$OPTARG";;
240 v) verbose=1;;
241 c) create=1;;
242 i) ns_info "$OPTARG"; exit 0;;
243 k) ns_cleanup "$OPTARG"; exit 0;;
244 l) nslist; exit 0;;
245 h) usage; exit 0;;
246 *) usage; exit 1;;
247 esac
248 done
249 shift $((OPTIND-1))
250
251 # Calling ns(1) without argument will attach to the first available
252 # namespace. If no namespace is available, a new one will be spun up.
253 if [ "$create" -eq 1 -o -z "$(nslist)" ]; then
254 ns_pop
255 [ "$detach" -eq 1 ] && { echo $nsname; exit 0; }
256 fi
257
258 [ -z "$nsname" ] && nsname=$(nslist|head -n1)
259
260 # Pop a shell or whatever args provided
261 ns_exec ${@:-mksh -l}