I should add, I thought about adding commands like rcctl's `get|getdef|set` commands to do full interface configuration (`set nwid wapname` or `set bssid <value>`) but wanted to get feedback on the current approach before going to far down the rabbit hole.
--Aaron * Aaron Poffenberger <[email protected]> [2017-09-26 20:39:29 -0500]: > Attached is a cli utility to manage network locations. It's modeled > after rcctl(8). > > It doesn't attempt to replace netstart(8) or ifconfig(8). It works > with them by storing location information in a directory and > symlinking the hostname.if files to the hostname.if for the selected > location. > > At the moment if doesn't handle auto discovery of WAPs, but I have > code I wrote earlier I'm rewriting and integrating. > > netctl is written in pure shell so in theory it could be called at > boot time. To do that, I'd probably split netctl into two pieces > similar to the way rcctl(8) and rc.subr(8) work. One file for > inclusion in /etc and the main ctl for /usr. > > The code could be integrated into netstart at some later date. > > In addition to the netctl utility I've included a man page. > > Comments or feedback would be appreciated. > > --Aaron > > --- /dev/null Tue Sep 26 20:26:45 2017 > +++ netctl Tue Sep 26 20:16:28 2017 > @@ -0,0 +1,450 @@ > +#!/bin/sh > +# > +# $OpenBSD$ > +# > +# Copyright (c) 2017 Aaron Poffenberger <[email protected]> > +# > +# Permission to use, copy, modify, and distribute this software for any > +# purpose with or without fee is hereby granted, provided that the above > +# copyright notice and this permission notice appear in all copies. > +# > +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > +# cmd OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS cmd, ARISING OUT OF > +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > +# > + > +# Turn off Strict Bourne shell mode. > +set +o sh > + > +readonly __progname="netctl" > +readonly TRUE=0 > +readonly FALSE=1 > +readonly HN_DIR=${HN_DIR:-/etc/hostname.d} > + > +# Log an error > +# Usage: log_err msg [exit code] > +# Cheerfully copied from rc.subr(8) > +# Copyright (c) 2010, 2011, 2014-2017 Antoine Jacoutot > <[email protected]> > +# Copyright (c) 2010, 2011 Ingo Schwarze <[email protected]> > +# Copyright (c) 2010, 2011, 2014 Robert Nagy <[email protected]> > +log_err() { > + [ -n "${1}" ] && echo "${1}" 1>&2 > + [ -n "${2}" ] && exit "${2}" || exit 1 > +} > + > +# Log a warning > +# Usage: log_err msg [exit code] > +log_warning() { > + log_msg "${1}" 1>&2 > +} > + > +# Log a message > +# Usage: log_msg msg [right justify length] > +log_msg() { > + local -R${2:-0} _msg="${1}" > + > + [ -n "${_msg}" ] && echo "${_msg}" > +} > + > +# Log a message to stdout or stderr > +# Usage: usage [2] > +usage() { > + echo \ > + "usage: netctl [-h] > + netctl ls [lsarg ...] > + netctl create|delete location ... > + netctl [-dr] switch location [interface ...] > + netctl enable|disable [configuration ...] > + netctl start|stop|restart [interface ...] > + netctl [-v] scan [interface ...]" 1>&${1:-1} > +} > + > +# Get interface configuration > +# Expects variables to be typeset/local from calling fn > +# Usage: get_if_conf if1 > +get_if_conf() { > + # use co-process to preserve values set in while loop > + ifconfig $1 |& > + while IFS=' ' read -r -p _l; do > + _key=${_l%%*([[:blank:]])*(:) *} > + _val=${_l##*([[:blank:]])*${_key}*(:)*([[:blank:]])} > + > + [[ ${_key} == 'groups' ]] && _groups=${_val} > + [[ ${_key} == 'media' ]] && _media=${_val} > + [[ ${_key} == 'status' ]] && _status=${_val} > + [[ ${_key} == 'ieee80211' ]] && _ieee80211=${_val} > + [[ ${_key} == 'inet' ]] && _inet=${_val} > + [[ ${_key} == 'inet6' ]] && _inet6=${_val} > + done > +} > + > +# Get interfaces > +# Expects variable _ifs to be typeset/local from calling fn > +# Usage: get_ifs if1 > +get_ifs() { > + local _if _excl_keys > + # exclude network pseudo-devices > + set -A _pseudo_devices $(ifconfig -C) > + > + # use co-process to preserve values set in while loop > + ifconfig $1 |& > + while IFS=' ' read -r -p _l; do > + [[ "${_l}" == *flags* ]] || continue > + > + _if=${_l%%*([[:blank:]])*():*} > + > + [ -z "${_if}" ] && continue > + > + #_if=${_l%%*([[:blank:]])*():*} > + > + # exclude if-type (san _if num) in pseudo-devices > + [[ " ${_pseudo_devices[*]}0 " == *" ${_if%%*([[:digit:]])} "* > ]] && > + continue > + > + _ifs[${#_ifs[*]}]="${_if}" > + done > + > + [ -n "${_ifs}" ] || return 1 > +} > + > +get_locations() { > + local _l > + > + ls -p "${HN_DIR}" |& > + # use co-process to preserve values set in while loop > + while IFS=' ' read -r -p _l ; do > + [[ "${_l}" == *([[:blank:]])*/ ]] || continue > + _locations[${#_locations[*]}]="${_l%%/}" > + done > +} > + > +get_configurations() { > + local _l _location > + > + _location="$1" > + > + ls -p "${HN_DIR}/${_location}" |& > + # use co-process to preserve values set in while loop > + while IFS=' ' read -r -p _l ; do > + [[ "${_l}" == *([[:blank:]])*[!/] ]] || continue > + > + _configurations[${#_configurations[*]}]="${_l}" > + done > +} > + > +# Restart interface > +# Usage: if_restart if1 > +if_restart() { > + if_stop $1 && if_start $1 > +} > + > +# Start interface > +# Usage: if_start if1 > +if_start() { > + local _if > + > + _if=$1 > + [ ${dryrun} -eq 1 ] && > + log_msg /bin/sh /etc/netstart -p ${_if} || > + /bin/sh /etc/netstart ${_if} > +} > + > +# Stop interface > +# Usage: if_stop if1 > +if_stop() { > + local _options _groups _media _status _ieee80211 _inet _inet6 \ > + _key _wlan_keys _inet_keys _mode_keys _if > + > + set -A _wlan_keys -- nwid chan mode nwkey powersave wpakey wpa > + > + _if=$1 > + get_if_conf ${_if} > + > + # remove mode if the interface is wireless > + [ -n "${_ieee80211}" ] && _options="${_options} -mode" > + > + for _key in ${_wlan_keys[*]} ; do > + [[ "${_ieee80211}" == *"${_key}"* ]] && > + _options="${_options} -${_key}" > + done > + > + [ -n "${_inet}" ] && _options="${_options} -inet" > + [ -n "${_inet6}" ] && _options="${_options} -inet6" > + > + [ ${dryrun} -eq 1 ] && > + log_msg ifconfig ${_if} ${_options} down || > + ifconfig ${_if} ${_options} down > +} > + > +ls_locations() { > + local _l _locations > + set -A _locations > + > + get_locations > + > + log_msg "locations:" > + for _l in "${_locations[@]}" ; do > + log_msg "\t${_l}" > + done > +} > + > +ls_configurations(){ > + local _c _configurations _l _locations > + set -A _locations > + set -A _configurations > + > + [[ -n "$@" ]] && set -A _locations "$@" || > + get_locations > + > + log_msg "configurations:" > + for _l in "${_locations[@]}" ; do > + get_configurations "${_l}" > + > + log_msg "\t${_l}:" > + for _c in "${_configurations[@]}" ; do > + log_msg "\t\t${_c}" > + done > + > + set -A _configurations > + done > +} > + > +ls_interfaces(){ > + local _if _ifs > + set -A _ifs > + > + get_ifs > + > + log_msg "interfaces:" > + for _if in "${_ifs[@]}" ; do > + log_msg "\t${_if}" > + done > +} > + > +# Get interface details > +# Expects variable _nwids to be typeset/local from calling fn > +# Usage: scan if1 > +scan() { > + local _nwid _i _iselem _if _verbose > + > + _if=$1 > + _verbose=$2 > + # use co-process to preserve values set in while loop > + ifconfig ${_if} scan |& > + while IFS=' ' read -r -p _l ; do > + [[ "${_l}" == *([[:blank::]])nwid* ]] || continue > + > + [[ "${_verbose:-0}" -eq 1 ]] && > + _nwids[${#_nwids[*]}]="${_l%%*([[:blank:]])*()% *}%" && > + continue > + > + [[ "${_verbose:-0}" -eq 2 ]] && > + _nwids[${#_nwids[*]}]="${_l}" && > + continue > + > + _nwid=${_l%%*([[:blank:]])*()chan *} > + _nwid=${_nwid##*nwid *()} > + > + [[ ${_nwid} == '""' ]] && continue > + > + > + _iselem=0 && _i=0 > + while ((_i < ${#_nwids[*]})) ; do > + [[ "${_nwids[_i]}" == "${_nwid}" ]] && > + _iselem=1 && break > + ((_i++)) > + done > + [ ${_iselem} -eq 0 ] && _nwids[${#_nwids[*]}]="${_nwid}" > + done > + > + [ -n "${_nwids}" ] || return 1 > +} > + > +typeset -i delete=0 dryrun=0 restart=0 verbose=0 > +while getopts :dhnqrv opt ; do > + case ${opt} in > + d) > + delete=1;; > + h) > + usage > + exit > + ;; > + n) > + dryrun=1;; > + q) > + quiet=1;; > + r) > + restart=1;; > + v) > + ((verbose++)) > + cmd="scan";; > + :) > + log_msg ${__progname}: option requires an argument \ > + -- ${OPTARG} > + usage 2 > + exit > + ;; > + \?) > + log_msg ${__progname}: invalid option -- ${OPTARG} > + usage 2 > + exit > + ;; > + esac > +done > +shift $(( OPTIND - 1 )) > +cmd=$1 > + > +case ${cmd} in > + ls) > + shift 1 > + lsarg="$1" > + shift 1 > + lsparms="$@" > + [[ ${lsarg} == @(all|locations|configurations|interfaces) ]] || > + usage 2 > + ;; > + create) > + shift 1 > + locations="$@" > + ;; > + delete) > + shift 1 > + locations="$@" > + ;; > + switch) > + shift 1 > + location="$1" > + [ -d "${HN_DIR}/${location}" ] || usage 2 > + shift 1 > + ifs="$@" > + get_ifs ${ifs} # throws its own errors > + ifs="${_ifs[@]}" > + ;; > + enable|disable|start|stop|restart) > + shift 1 > + ifs="$@" > + get_ifs ${ifs} # throws its own errors > + ifs="${_ifs[@]}" > + ;; > + scan) > + shift 1 > + ifs="$@" > + get_ifs "${ifs:-wlan}" # throws its own errors > + ifs="${_ifs[@]}" > + ;; > + *) > + usage 2 > + ;; > +esac > + > +case ${cmd} in > + ls) > + case ${lsarg} in > + all) > + ls_locations > + ls_configurations > + ls_interfaces > + ;; > + locations) > + ls_locations > + ;; > + configurations) > + ls_configurations ${lsparms};; > + interfaces) > + ls_interfaces;; > + *) > + usage 2 > + ;; > + esac > + ;; > + create) > + for _loc in ${locations} ; do > + mkdir -p "${HN_DIR}/${_loc}" || > + log_warning "Unable to create location ${_loc}" > + done > + ;; > + delete) > + for _loc in ${locations} ; do > + rm -rf "${HN_DIR}/${_loc}" || > + log_warning "Unable to delete location ${_loc}" > + done > + ;; > + switch) > + for _if in ${ifs} ; do > + _from="${HN_DIR}/${location}/hostname.${_if}" > + _to="/etc/hostname.${_if}" > + [ ! -f "${_to}" ] && > + [ -f "/etc/hostname.${_if}.disabled" ] && > + _to="/etc/hostname.${_if}.disabled" > + > + [ ! -f "${_from}" ] && > + [ ${delete} -eq 1 ] && > + rm -f "${_to}" && > + continue > + > + [ -f "${_from}" ] && > + ln -fs "${_from}" "${_to}" && > + log_msg "Switch ${_if}" || > + log_warning "No configuration for > ${_if}" > + > + [ -f "${_to}" ] && > + [ ${restart} -eq 1 ] && > + if_restart ${_if} > + done > + ;; > + enable) > + for _if in ${ifs} ; do > + _from="/etc/hostname.${_if}.disabled" > + _to="/etc/hostname.${_if}" > + [ -f "${_from}" ] || continue > + > + [ -f "${_from}" ] && ! [ -f "${_to}" ] && > + mv "${_from}" "${_to}" || > + log_warning "Unable to enable ${_if}" > + done > + ;; > + disable) > + for _if in ${ifs} ; do > + _from="/etc/hostname.${_if}" > + _to="/etc/hostname.${_if}.disabled" > + [ -f "${_from}" ] || continue > + > + [ -f "${_from}" ] && > + mv "${_from}" "${_to}" || > + log_warning "Unable to disable ${_if}" > + done > + ;; > + start) > + for _if in ${ifs} ; do > + if_start ${_if} > + done > + ;; > + stop) > + for _if in ${ifs} ; do > + if_stop ${_if} > + done > + ;; > + restart) > + for _if in ${ifs} ; do > + if_restart ${_if} > + done > + ;; > + scan) > + local _nwids > + set -A _nwids > + for _if in ${ifs} ; do > + log_msg "${_if}:" > + scan ${_if} ${verbose} > + for _nwid in "${_nwids[@]}" ; do > + log_msg "\t${_nwid}" > + done > + done > + ;; > + *) > + usage 2 > + ;; > +esac > --- /dev/null Tue Sep 26 20:26:45 2017 > +++ netctl.8 Tue Sep 26 20:16:28 2017 > @@ -0,0 +1,270 @@ > +.\" $OpenBSD$ > +.\" > +.\" Copyright (c) 2017 Aaron Poffenberger <[email protected]> > +.\" > +.\" Permission to use, copy, modify, and distribute this software for any > +.\" purpose with or without fee is hereby granted, provided that the above > +.\" copyright notice and this permission notice appear in all copies. > +.\" > +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > +.\" > +.Dd $Mdocdate: Sep 23 2017 $ > +.Dt NETCTL 8 > +.Os > +.Sh NAME > +.Nm netctl > +.Nd configure and control hostname.if definitions > +.Sh SYNOPSIS > +.Nm netctl > +.Op Fl h > +.Nm netctl > +.Cm ls > +.Op Ar lsarg ... > +.Nm netctl > +.Cm create|delete > +.Ar location ... > +.Nm netctl > +.Op Fl dr > +.Cm switch > +.Ar location > +.Op Ar interface ... > +.Nm netctl > +.Cm enable|disable > +.Op Ar configuration ... > +.Nm netctl > +.Cm start|stop|restart > +.Op Ar interface ... > +.Nm netctl > +.Op Fl v > +.Cm scan > +.Op Ar interface ... > +.Sh DESCRIPTION > +The > +.Nm > +utility is used to manage network locations, interface configuration > +files, and to start, stop, or restart interfaces. > +.Pp > +.Nm > +works with > +.Xr ifconfig 8 > +and > +.Xr netstart 8 . > +It does not replace them. > +.Pp > +The following options are available: > +.Bl -tag -width Ds > +.It Fl h > +Help message. > +.It Cm ls Op Ar lsarg ... > +Display a list of locations, configurations and interfaces matching > +.Ar lsarg , > +which can be one of: > +.Bl -tag -width "interfaces" -offset indent -compact > +.It Cm all > +all locations, configurations and interfaces > +.It Cm locations > +all locations > +.It Cm configurations Op Ar location ... > +all configurations for the current > +.Ar location > +or locations > +.It Cm interfaces > +all interfaces (exluding pseudo-devices) > +.El > +.It Ar location > +The > +.Ar location > +parameter is the name of a collection of related > +.Ar interface > +configuration files. > +.Pp > +.Nm > +locations are stored as directories in > +.Pa /etc/hostname.d . > +See > +.Sx FILES . > +.Pp > +.Sy N.B . > +May not include network pseudo-devices. > +.It Ar configuration > +The > +.Ar configuration > +parameter is a > +.Xr hostname.if 5 > +file in a > +.Ar location > +directory. > +See > +.Sx FILES . > +.It Ar interface > +The > +.Ar interface > +parameter is an interface > +.Dq name unit > +as defined in > +.Xr ifconfig 8 > +which includes groups. > +.El > +.Sh LOCATION > +The following commands are available for working with network locations: > +.Bl -tag -width Ds > +.It Cm create Ar location ... > +Create one or more locations. > +.It Cm delete Ar location ... > +Delete one or more locations. > +.Pp > +.Sy N.B . > +Deletes all > +.Ar interface > +.Ar configuration > +files in > +.Ar location . > +.It Oo Fl dr Oc Cm switch Ar location Oo Ar interface ... Oc > +Switch to > +.Ar location . > +.It Fl d > +Delete > +.Pa /etc/hostname.if > +not found in > +.Ar location . > +.Pp > +.Sy N.B . > +Does not affect actual interface. > +Removes symlink from previous location. > +Otherwise the symlink to the previous location configuration is left in > place. > +.Pp > +.Sy Default : > +leave in place. > +.It Fl r > +Restart the interface(s) after switching. > +.El > +.Sh CONFIGURATION > +The following commands are available for working with interface > configurations: > +.Bl -tag -width Ds > +.It Cm enable Ar configuration ... > +Enable an interface > +.Ar configuration . > +.It Cm disable Ar configuration ... > +Disable an interface > +.Ar configuration . > +.El > +.Sh INTERFACE > +The following commands are available for working with interfaces: > +.Bl -tag -width Ds > +.It Cm start Op Ar interface ... > +Start one or more interfaces. > +.It Cm stop Op Ar interface ... > +Stop one or more interfaces. > +.It Cm restart Op Ar interface ... > +Restart one or more interfaces. > +.It Oo Fl v Oc Cm scan Op Ar interface ... > +Scan for wireless access point with one or more interfaces. > +.It Fl v > +Verbose scan. > +.It Fl vv > +Detailed scan (Same as > +.Cm ifconfig Ar if Ar scan Ns > +). > +.El > +.Sh ENVIRONMENT > +.Bl -tag -width MANPATHX > +.It Ev HN_DIR > +Directory to find > +.Ar location > +directories and interface configuration files. > +.El > +.Sh FILES > +.Bl -tag -width "/etc/hostname.d/*/hostname.XXX" -compact > +.It Pa /etc/hostname.d > +default > +.Ar location > +directory. > +.Pp > +.It Pa /etc/hostname.d/*/hostname.XXX > +interface-specific > +.Ar configuration > +files by location. > +.El > +.Sh EXAMPLES > +Create new location: > +.Bd -literal -offset indent > +$ doas netctl create home > +.Ed > +.Pp > +Start > +.Ar em0 : > +.Bd -literal -offset indent > +$ doas netctl start em0 > +.Ed > +.Pp > +Switch location and restart all interfaces: > +.Bd -literal -offset indent > +netctl -r switch home > +.Ed > +.Pp > +Switch location and restart > +.Ar iwm0 : > +.Bd -literal -offset indent > +$ doas netctl -r switch home iwm0 > +.Ed > +.Pp > +Switch location and remove unconfigured interfaces: > +.Bd -literal -offset indent > +$ ls /etc/hostname.{em,iwm}0 > +/etc/hostname.em0@ /etc/hostname.iwm0@ > + > +$ ls /etc/hostname.d/work > +/etc/hostname.d/work/hostname.iwm0 > + > +$ doas netctl -d switch work > + > +$ ls /etc/hostname.{em,iwm}0 > +/etc/hostname.iwm0@ > +.Ed > +.Pp > +Scan for wireless access points with > +.Ar iwm0 : > +.Bd -literal -offset indent > +$ doas netctl -v scan iwm0 > +iwm0: > + supersecurewap > + notsosecurewap > + "Silly Wap" > +.Ed > +.Sh DIAGNOSTICS > +.Nm > +utility always exits 0. > +.Sh SEE ALSO > +.Xr hostname.if 5 , > +.Xr ifconfig 8 , > +.Xr netstart 8 > +.Sh HISTORY > +.Nm > +is a new utility but it draws inspiration and style from > +.Xr rcctl 8 > +and > +.Xr netstart 8 . > +.Pp > +A great deal of credit is due to Antoine Jacoutot, Ingo Schwarze, and > +Robert Nagy for their work on > +.Xr rcctl 8 > +and > +.Xr netstart 8 . > +.Sh AUTHORS > +.An -nosplit > +The > +.Nm > +utility was written by > +.An Aaron Poffenberger Aq Mt [email protected] . > +.Sh BUGS > +.Nm > +should prevent users from running commands that require superuser. > +.Nm > +should al work with some network pseudo-devices like > +.Xr trunk 4 .
