My take on a network manager
There’s one thing that I am really missing under OpenBSD, a network manager which seemlessly handles running around with my laptop. So my main itches to scratch are doing magic things at boot and resume so I don’t have to bother with fiddling with hostname.if(5) ever.
My first take on tackling that problem actually was working ok’ish but depended on sqlite3 and after sqlite3 left base the solution started to annoy me everytime I moved to a current snapshot and sqlite3 stoppped working or was unavailable from within bsd.rd.
So I started to look around what other people did to get rid of those problems which led me to netctl. I like that netctl is nothing but a shell script. I dislike that it didn’t work at boot time.
As my first take already worked at boot time, reducing my hostname.if(5) to being only 2 lines:
up
!/etc/netmanager \$if
I decided to rewrite the script, reusing chunks of netstart(8) in order to keep
the fileformat in a well-known format. /etc/netmanager
basically searches below
/etc/hostname.d
for a file matching the given – or autodetected – network ID to
connect to. So without further ado here it is:
#!/bin/sh -
# parse_hn_line() and ifstart() are taken from /etc/netstart revision 1.195 with
# some small additions (basically addition of $_nwid and the removal of unneeded routines)
set +o sh
usage() {
cat <<EOF >&2
usage: /etc/netmanager [<nwid>] <if>
<nwid> network to connect to
<if> interface to connect
netmanager searches for <nwid>.nwid in /etc/hostname.d, parses the file and
feeds ifconfig(8) accordingly. <nwid>.nwid has the same format as hostname.if(5).
If no <nwid> has been given netmanager issues a scan for access points and
searches for a matching <nwid>.nwid file.
EOF
exit 2
}
# Parse and "unpack" a hostname.if(5) line given as positional parameters.
# Fill the _cmds array with the resulting interface configuration commands.
parse_hn_line() {
local _af=0 _name=1 _mask=2 _bc=3 _prefix=2 _c _cmd _prev _daddr
set -A _c -- "$@"
set -o noglob
case ${_c[_af]} in
''|*([[:blank:]])'#'*)
return
;;
inet) ((${#_c[*]} > 1)) || return
[[ ${_c[_name]} == alias ]] && _mask=3 _bc=4
[[ -n ${_c[_mask]} ]] && _c[_mask]="netmask ${_c[_mask]}"
if [[ -n ${_c[_bc]} ]]; then
_c[_bc]="broadcast ${_c[_bc]}"
[[ ${_c[_bc]} == *NONE ]] && _c[_bc]=
fi
_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
;;
inet6) ((${#_c[*]} > 1)) || return
if [[ ${_c[_name]} == autoconf ]]; then
_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
V6_AUTOCONF=true
return
fi
[[ ${_c[_name]} == alias ]] && _prefix=3
[[ -n ${_c[_prefix]} ]] && _c[_prefix]="prefixlen ${_c[_prefix]}"
_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
;;
dest) ((${#_c[*]} == 2)) && _daddr=${_c[1]} || return
_prev=$((${#_cmds[*]} - 1))
((_prev >= 0)) || return
set -A _c -- ${_cmds[_prev]}
_name=3
[[ ${_c[_name]} == alias ]] && _name=4
_c[_name]="${_c[_name]} $_daddr"
_cmds[$_prev]="${_c[@]}"
;;
dhcp) _c[0]=
_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]} down;dhclient $_if"
V4_DHCPCONF=true
;;
'!'*) _cmd=$(print -- "${_c[@]}" | sed 's/\$if/'$_if'/g')
_cmds[${#_cmds[*]}]="${_cmd#!}"
;;
*) _cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
;;
esac
unset _c
set +o noglob
}
# Start a single interface.
# Usage: ifstart if1
ifstart() {
local _nwid=$1 _hn=/etc/hostname.d/$_nwid.nwid _cmds _i=0 _line _stat
set -A _cmds
if [[ ! -f $_hn ]]; then
print -u2 "${0##*/}: $_hn: No such file or directory."
return
fi
# Not using stat(1), we can't rely on having /usr yet.
set -A _stat -- $(ls -nL $_hn)
if [[ "${_stat[0]}${_stat[2]}${_stat[3]}" != *---00 ]]; then
print -u2 "WARNING: $_hn is insecure, fixing permissions."
chmod -LR o-rwx $_hn
chown -LR root:wheel $_hn
fi
# Parse the hostname.if(5) file and fill _cmds array with interface
# configuration commands.
set -o noglob
while IFS= read -- _line; do
parse_hn_line $_line
done <$_hn
# Apply the interface configuration commands stored in _cmds array.
while ((_i < ${#_cmds[*]})); do
if $PRINT_ONLY; then
print -r -- "${_cmds[_i]}"
else
eval "${_cmds[_i]}"
fi
((_i++))
done
unset _cmds
set +o noglob
}
autoselect() {
local _if=$1
ifconfig $_if down
ifconfig $_if scan | awk '
/ieee80211:/{ next }
/nwid/{
start=index($0,"nwid")+length("nwid ")
end=index($0,"chan ")
nwid=substr($0,start,end-start)
if (nwid)
print nwid
}' | while read nwid; do
{ ifconfig $_if | grep 'status: active' >/dev/null ;} && return
[[ -r /etc/hostname.d/"$nwid".nwid ]] && \
connect "$nwid" $_if
done
}
connect() {
local _nwid="$1" _if=$2;
if [[ -r /etc/hostname.d/"$_nwid".nwid ]]; then
printf "%s: connecting to %s\n" "$_if" "$_nwid"
ifstart $_nwid $_if
fi
}
PRINT_ONLY=false
if [ $# -eq 1 ]; then
autoselect $1
elif [ $# -eq 2 ]; then
connect $1 $2
else
usage
fi
exit 0
As I am currently running the latest snapshot I haven’t tested if it’s working
under bsd.rd
, too, but I am actually very optimistic. So this is my take on
a network manager that does what I am expecting of it, is base only and
hopefully even works in the installer environment, making the whole experience
basically frictionless. At least in my workflow.
Keep in mind this is just a quick hack but maybe somebody finds it useful.
--EOF