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 .

Reply via email to