My take on a network manager

📆
🏷
openbsd, netmanager

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