Index: libexec/flua/Makefile =================================================================== --- libexec/flua/Makefile +++ libexec/flua/Makefile @@ -24,6 +24,10 @@ CFLAGS+= -I${SRCTOP}/lib/liblua -I${.CURDIR}/modules -I${LUASRC} CFLAGS+= -DLUA_PROGNAME="\"${PROG}\"" +SRCS+= lua_ifconfig.c +CFLAGS+= -I${SRCTOP}/lib/libifconfig +LIBADD+= ifconfig sysdecode + # readline bits; these aren't needed if we're building a bootstrap flua, as we # don't expect that one to see any REPL usage. .if !defined(BOOTSTRAPPING) Index: libexec/flua/linit_flua.c =================================================================== --- libexec/flua/linit_flua.c +++ libexec/flua/linit_flua.c @@ -36,6 +36,7 @@ #include "lauxlib.h" #include "lfs.h" #include "lposix.h" +#include "lua_ifconfig.h" #include "lua_ucl.h" /* @@ -57,6 +58,7 @@ {LUA_BITLIBNAME, luaopen_bit32}, #endif /* FreeBSD Extensions */ + {"ifconfig", luaopen_ifconfig}, {"lfs", luaopen_lfs}, {"posix.sys.stat", luaopen_posix_sys_stat}, {"posix.unistd", luaopen_posix_unistd}, Index: libexec/flua/modules/lua_ifconfig.h =================================================================== --- libexec/flua/modules/lua_ifconfig.h +++ libexec/flua/modules/lua_ifconfig.h @@ -0,0 +1,10 @@ +/*- + * This file is in the public domain. + */ +/* $FreeBSD$ */ + +#pragma once + +#include + +int luaopen_ifconfig(lua_State *L); Index: libexec/flua/modules/lua_ifconfig.c =================================================================== --- libexec/flua/modules/lua_ifconfig.c +++ libexec/flua/modules/lua_ifconfig.c @@ -0,0 +1,1462 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2020, Ryan Moeller + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* $FreeBSD$ */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#define IFCONFIG_HANDLE_META "ifconfig_handle_t" +#define STRUCT_IFADDRS_META "struct ifaddrs *" + +#define IFFBITS \ + "\020" \ + "\001UP" \ + "\002BROADCAST" \ + "\003DEBUG" \ + "\004LOOPBACK" \ + "\005POINTOPOINT" \ + "\007RUNNING" \ + "\010NOARP" \ + "\011PROMISC" \ + "\012ALLMULTI" \ + "\013OACTIVE" \ + "\014SIMPLEX" \ + "\015LINK0" \ + "\016LINK1" \ + "\017LINK2" \ + "\020MULTICAST" \ + "\022PPROMISC" \ + "\023MONITOR" \ + "\024STATICARP" + +#define IFCAPBITS \ + "\020" \ + "\001RXCSUM" \ + "\002TXCSUM" \ + "\003NETCONS" \ + "\004VLAN_MTU" \ + "\005VLAN_HWTAGGING" \ + "\006JUMBO_MTU" \ + "\007POLLING" \ + "\010VLAN_HWCSUM" \ + "\011TSO4" \ + "\012TSO6" \ + "\013LRO" \ + "\014WOL_UCAST" \ + "\015WOL_MCAST" \ + "\016WOL_MAGIC" \ + "\017TOE4" \ + "\020TOE6" \ + "\021VLAN_HWFILTER" \ + "\023VLAN_HWTSO" \ + "\024LINKSTATE" \ + "\025NETMAP" \ + "\026RXCSUM_IPV6" \ + "\027TXCSUM_IPV6" \ + "\031TXRTLMT" \ + "\032HWRXTSTMP" \ + "\033NOMAP" \ + "\034TXTLS4" \ + "\035TXTLS6" + +#define IN6BITS \ + "\020" \ + "\001anycast" \ + "\002tenative" \ + "\003duplicated" \ + "\004detached" \ + "\005deprecated" \ + "\007nodad" \ + "\010autoconf" \ + "\011temporary" \ + "\012prefer_source" + +#define ND6BITS \ + "\020" \ + "\001nud" \ + "\002accept_rtadv" \ + "\003prefer_source" \ + "\004ifdisabled" \ + "\005dont_set_ifroute" \ + "\006auto_linklocal" \ + "\007no_radr" \ + "\010no_prefer_iface" \ + "\011no_dad" \ + "\012ipv6_only" \ + "\013ipv6_only" + +#define LAGGHASHBITS \ + "\020" \ + "\001l2" \ + "\002l3" \ + "\003l4" + +static const char *carp_states[] = { CARP_STATES }; +static const struct lagg_protos lagg_protos[] = LAGG_PROTOS; + +/* + * Create an array of the set flags in v, using the kernel %b format bits. + */ +static void +push_flags_array(lua_State *L, unsigned v, const char *bits) +{ + int i, len; + + lua_newtable(L); + + /* Skip numeric format. */ + ++bits; + + while ((i = *bits++) != '\0') { + if ((v & (1 << (i - 1))) != 0) { + for (len = 0; bits[len] > 32; ++len); + lua_pushlstring(L, bits, len); + lua_rawseti(L, -2, i); + bits += len; + } else { + for (; *bits > 32; ++bits); + } + } +} + +/* + * Decode an array of the flags to set and return as an int. + */ +static int +pop_flags_array(lua_State *L, int index, const char *bits) +{ + int v = 0; + + /* Skip numeric format. */ + ++bits; + + lua_pushnil(L); /* first key */ + while (lua_next(L, index) != 0) { + const char *flag, *flags = bits; + int i, len; + + luaL_checkstring(L, index + 2); + + flag = lua_tostring(L, index + 2); + lua_pop(L, 1); + while ((i = *flags++) != '\0') { + for (len = 0; flags[len] > 32; ++len); + if (strncmp(flag, flags, len) == 0) { + v |= 1 << (i - 1); + break; + } + flags += len; + } + } + + return (v); +} + +/* + * Push a table describing media information. + */ +static void +push_media_info(lua_State *L, int ifmw) +{ + char opts[80]; + int index; + + lua_newtable(L); + index = lua_gettop(L); + + lua_pushstring(L, ifconfig_media_get_type(ifmw)); + lua_setfield(L, index, "type"); + + lua_pushstring(L, ifconfig_media_get_subtype(ifmw)); + lua_setfield(L, index, "subtype"); + + ifconfig_media_get_options_string(ifmw, opts, sizeof opts); + lua_pushstring(L, opts); + lua_setfield(L, index, "options"); +} + +/* + * Push a table describing an AF_INET address or nil if error. + */ +static void +push_inet4_addrinfo(lua_State *L, ifconfig_handle_t *h, struct ifaddrs *ifa) +{ + char addr_buf[NI_MAXHOST]; + struct ifconfig_inet_addr addr; + int index; + + if (ifconfig_inet_get_addrinfo(h, ifa->ifa_name, ifa, &addr) != 0) { + lua_pushnil(L); + return; + } + + lua_newtable(L); + index = lua_gettop(L); + + /* TODO: can this just be inet_ntoa? */ + inet_ntop(AF_INET, &addr.sin->sin_addr, addr_buf, sizeof addr_buf); + lua_pushstring(L, addr_buf); + lua_setfield(L, index, "inet"); + + if (addr.dst != NULL) { + lua_pushstring(L, inet_ntoa_r(addr.dst->sin_addr, addr_buf, + sizeof addr_buf)); + lua_setfield(L, index, "destination"); + } + + lua_pushstring(L, inet_ntoa_r(addr.netmask->sin_addr, addr_buf, + sizeof addr_buf)); + lua_setfield(L, index, "netmask"); + + if (addr.broadcast != NULL) { + lua_pushstring(L, inet_ntoa_r(addr.broadcast->sin_addr, + addr_buf, sizeof addr_buf)); + lua_setfield(L, index, "broadcast"); + } + + if (addr.vhid != 0) { + lua_pushinteger(L, addr.vhid); + lua_setfield(L, index, "vhid"); + } +} + +/* + * Push a table describing an AF_INET6 address or nil if error. + */ +static void +push_inet6_addrinfo(lua_State *L, ifconfig_handle_t *h, struct ifaddrs *ifa) +{ + char addr_buf[NI_MAXHOST]; + struct ifconfig_inet6_addr addr; + struct timespec now; + int index; + + if (ifconfig_inet6_get_addrinfo(h, ifa->ifa_name, ifa, &addr) != 0) { + lua_pushnil(L); + return; + } + + lua_newtable(L); + index = lua_gettop(L); + + if (getnameinfo((struct sockaddr *)addr.sin6, addr.sin6->sin6_len, + addr_buf, sizeof addr_buf, NULL, 0, NI_NUMERICHOST) != 0) + /* TODO: can this just be inet_ntoa? */ + inet_ntop(AF_INET6, &addr.sin6->sin6_addr, addr_buf, + sizeof addr_buf); + lua_pushstring(L, addr_buf); + lua_setfield(L, index, "inet6"); + + if (addr.dstin6 != NULL) { + inet_ntop(AF_INET6, addr.dstin6, addr_buf, sizeof addr_buf); + lua_pushstring(L, addr_buf); + lua_setfield(L, index, "destination"); + } + + lua_pushinteger(L, addr.prefixlen); + lua_setfield(L, index, "prefixlen"); + + if (addr.sin6->sin6_scope_id != 0) { + lua_pushinteger(L, addr.sin6->sin6_scope_id); + lua_setfield(L, index, "scopeid"); + } + + push_flags_array(L, addr.flags, IN6BITS); + lua_setfield(L, index, "flags"); + + clock_gettime(CLOCK_MONOTONIC_FAST, &now); + if (addr.lifetime.ia6t_preferred || addr.lifetime.ia6t_expire) { + lua_pushinteger(L, MAX(0l, + addr.lifetime.ia6t_preferred - now.tv_sec)); + lua_setfield(L, index, "preferred_lifetime"); + + lua_pushinteger(L, MAX(0l, + addr.lifetime.ia6t_expire - now.tv_sec)); + lua_setfield(L, index, "valid_lifetime"); + } + + if (addr.vhid != 0) { + lua_pushinteger(L, addr.vhid); + lua_setfield(L, index, "vhid"); + } +} + +/* + * Push a table describing an AF_LINK address or nil if error. + */ +static void +push_link_addrinfo(lua_State *L, ifconfig_handle_t *h, struct ifaddrs *ifa) +{ + char addr_buf[NI_MAXHOST]; + struct sockaddr_dl *sdl; + int index, n; + + sdl = (struct sockaddr_dl *)ifa->ifa_addr; + if (sdl == NULL || sdl->sdl_alen <= 0) { + lua_pushnil(L); + return; + } + + lua_newtable(L); + index = lua_gettop(L); + + switch (sdl->sdl_type) { + case IFT_ETHER: + case IFT_L2VLAN: + case IFT_BRIDGE: + if (sdl->sdl_alen != ETHER_ADDR_LEN) + break; + ether_ntoa_r((struct ether_addr *)LLADDR(sdl), addr_buf); + lua_pushstring(L, addr_buf); + lua_setfield(L, index, "ether"); + break; + default: + n = sdl->sdl_nlen > 0 ? sdl->sdl_nlen + 1 : 0; + lua_pushstring(L, link_ntoa(sdl) + n); /* FIXME: link_ntoa_r */ + lua_setfield(L, index, "lladdr"); + break; + } +} + +/* + * Push a table describing an AF_LOCAL address. + */ +static void +push_local_addrinfo(lua_State *L, ifconfig_handle_t *h, struct ifaddrs *ifa) +{ + struct sockaddr_un *sun; + int index; + + lua_newtable(L); + index = lua_gettop(L); + + if (strlen(sun->sun_path) == 0) + lua_pushstring(L, "-"); + else + lua_pushstring(L, sun->sun_path); + lua_setfield(L, index, "path"); +} + +/* + * Check the stack for an ifconfig handle userdata at the given index. + */ +static ifconfig_handle_t * +l_ifconfig_checkhandle(lua_State *L, int index) +{ + ifconfig_handle_t *h, **hp; + + hp = luaL_checkudata(L, index, IFCONFIG_HANDLE_META); + assert(hp != NULL); + h = *hp; + luaL_argcheck(L, h != NULL, index, "invalid ifconfig handle"); + return (h); +} + +/* + * Check the stack for an ifaddrs userdata at the given index. + */ +static struct ifaddrs * +l_ifconfig_checkifaddrs(lua_State *L, int index) +{ + struct ifaddrs *ifa, **ifap; + + ifap = luaL_checkudata(L, index, STRUCT_IFADDRS_META); + assert(ifap != NULL); + ifa = *ifap; + luaL_argcheck(L, ifa != NULL, index, "invalid ifaddr"); + return (ifa); +} + +/* + * Invoke a callback function on the stack with the handle and accumulator + * from the stack and the given ifaddrs, leaving the handle, callback, and + * accumulator result on the stack after the call. + * + * This is used by the ifconfig_foreach_* functions to invoke a lua callback. + */ +static void +foreach_cb(ifconfig_handle_t *h, struct ifaddrs *ifa, void *udata) +{ + lua_State *L = udata; + struct ifaddrs **ifap; + + /* Stack: h,cb,acc */ + + /* Make copies of the callback and handle positioned for the call. */ + lua_pushvalue(L, 1); /* -> h,cb,acc,h */ + lua_pushvalue(L, 2); /* -> h,cb,acc,h,cb */ + lua_insert(L, 3); /* -> h,cb,cb,acc,h */ + lua_insert(L, 4); /* -> h,cb,cb,h,acc */ + + /* Push the ifa userdata. */ + ifap = lua_newuserdata(L, sizeof ifa); /* -> h,cb,cb,h,acc,ifa */ + assert(ifap != NULL); + *ifap = ifa; + luaL_getmetatable(L, STRUCT_IFADDRS_META); + lua_setmetatable(L, -2); + + /* Reposition the accumulator to be last so it can be optional. */ + lua_insert(L, 5); /* -> h,cb,cb,h,ifa,acc */ + + /* Invoke the callback. */ + lua_call(L, 3, 1); /* -> h,cb,acc */ +} + +/** Explicit close method + */ +static int +l_ifconfig_handle_close(lua_State *L) +{ + ifconfig_handle_t *h, **hp; + + hp = luaL_checkudata(L, 1, IFCONFIG_HANDLE_META); + assert(hp != NULL); + h = *hp; + if (h != NULL) { + ifconfig_close(h); + *hp = NULL; + } + return (0); +} + +/** Get error info + * @return errtype, errno, ioctlname? + */ +static int +l_ifconfig_handle_error(lua_State *L) +{ + ifconfig_handle_t *h; + ifconfig_errtype errtype; + + h = l_ifconfig_checkhandle(L, 1); + + errtype = ifconfig_err_errtype(h); + switch (errtype) { + case OK: + lua_pushnil(L); + return (1); + case OTHER: + lua_pushstring(L, "OTHER"); + break; + case IOCTL: + lua_pushstring(L, "IOCTL"); + break; + case SOCKET: + lua_pushstring(L, "SOCKET"); + break; + default: + lua_pushstring(L, ""); + break; + } + + lua_pushinteger(L, ifconfig_err_errno(h)); + + if (errtype == IOCTL) { + unsigned long req = ifconfig_err_ioctlreq(h); + lua_pushstring(L, sysdecode_ioctlname(req)); + return (3); + } + + return (2); +} + +/** Higher-order fold iterator over interfaces + * @param cb A callback function (handle, iface, udata) -> udata + * @param udata An initial accumulator value (may be nil) + * @return The final accumulator value returned by cb + */ +static int +l_ifconfig_foreach_iface(lua_State *L) +{ + const int MAXARGS = 3; + ifconfig_handle_t *h; + int n; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checktype(L, 2, LUA_TFUNCTION); + + /* Ensure the correct number of args. Extras are discarded. */ + n = lua_gettop(L); + if (n == (MAXARGS - 1)) + lua_pushnil(L); + else if (n > MAXARGS) + lua_pop(L, n - MAXARGS); + + if (ifconfig_foreach_iface(h, foreach_cb, L) != 0) + return luaL_error(L, "iteration failed"); + + return (1); +} + +/** Higher-order fold iterator over interface addresses + * @param ifa A STRUCT_IFADDRS_META userdata value from foreach_iface + * @param cb A callback function (handle, iface, udata) -> udata + * @param udata An initial accumulator value (may be nil) + * @return The final accumulator value returned by cb + */ +static int +l_ifconfig_foreach_ifaddr(lua_State *L) +{ + const int MAXARGS = 4; + ifconfig_handle_t *h; + struct ifaddrs *ifa; + int n; + + h = l_ifconfig_checkhandle(L, 1); + ifa = l_ifconfig_checkifaddrs(L, 2); + luaL_checktype(L, 3, LUA_TFUNCTION); + + /* Ensure the correct number of args. Extras are discarded. */ + n = lua_gettop(L); + if (n == (MAXARGS - 1)) + lua_pushnil(L); + else if (n > MAXARGS) + lua_pop(L, n - MAXARGS); + lua_remove(L, 2); + + ifconfig_foreach_ifaddr(h, ifa, foreach_cb, L); + + return (1); +} + +/** Get interface description + * @param name The name of an interface + * @return Description string if set, otherwise nil + */ +static int +l_ifconfig_get_description(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + char *desc; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_get_description(h, name, &desc) != 0) + lua_pushnil(L); + else + lua_pushstring(L, desc); + + return (1); +} + +/** Set interface description + * @param name The name of an interface + * @param desc Description string to set + * @return true if success, false if error + */ +static int +l_ifconfig_set_description(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name, *desc; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + luaL_checkstring(L, 3); + + name = lua_tostring(L, 2); + desc = lua_tostring(L, 3); + if (ifconfig_set_description(h, name, desc) != 0) + lua_pushboolean(L, false); + else + lua_pushboolean(L, true); + + return (1); +} + +/** Unset interface description + * @param name The name of an interface + * @return true if success, false if error + */ +static int +l_ifconfig_unset_description(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_unset_description(h, name) != 0) + lua_pushboolean(L, false); + else + lua_pushboolean(L, true); + + return (1); +} + +/** Set interface name + * @param name The name of an interface + * @param newname A new name for the interface + * @return true if success, false if error + */ +static int +l_ifconfig_set_name(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name, *newname; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + luaL_checkstring(L, 3); + + name = lua_tostring(L, 2); + newname = lua_tostring(L, 3); + if (ifconfig_set_name(h, name, newname) != 0) + lua_pushboolean(L, false); + else + lua_pushboolean(L, true); + + return (1); +} + +/** Get original interface name + * @param name The name of an interface + * @return Original interface name or nil if error + */ +static int +l_ifconfig_get_orig_name(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + char *orig_name; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_get_orig_name(h, name, &orig_name) != 0) + lua_pushnil(L); + else + lua_pushstring(L, orig_name); + + return (1); +} + +#if 0 /* setfib not implemented in libifconfig? */ +/** Set interface fib + * @param name The name of an interface + * @param fib A fib number to use + * @return true if success, false if error + */ +static int +l_ifconfig_set_fib(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + ptrdiff_t fib; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + luaL_checkinteger(L, 3); + + name = lua_tostring(L, 2); + fib = lua_tointeger(L, 3); + luaL_argcheck(L, INT_MAX < fib || fib < INT_MIN, 3, "fib out of range"); + if (ifconfig_set_fib(h, name, fib) != 0) + lua_pushboolean(L, false); + else + lua_pushboolean(L, true); + + return (1); +} +#endif + +/** Get interface fib + * @param name The name of an interface + * @return fib of the interface or nil if error + */ +static int +l_ifconfig_get_fib(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + int fib; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_get_fib(h, name, &fib) != 0) + lua_pushnil(L); + else + lua_pushinteger(L, fib); + + return (1); +} + +/** Set interface mtu + * @param name The name of an interface + * @param mtu A mtu to use + * @return true if success, false if error + */ +static int +l_ifconfig_set_mtu(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + ptrdiff_t mtu; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + luaL_checkinteger(L, 3); + + name = lua_tostring(L, 2); + mtu = lua_tointeger(L, 3); + luaL_argcheck(L, INT_MIN <= mtu && mtu <= INT_MAX, 3, "mtu out of range"); + if (ifconfig_set_mtu(h, name, mtu) != 0) + lua_pushboolean(L, false); + else + lua_pushboolean(L, true); + + return (1); +} + +/** Get interface mtu + * @param name The name of an interface + * @return mtu of the interface or nil if error + */ +static int +l_ifconfig_get_mtu(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + int mtu; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_get_mtu(h, name, &mtu) != 0) + lua_pushnil(L); + else + lua_pushinteger(L, mtu); + + return (1); +} + +/** Get interface nd6 info + * @param name The name of an interface + * @return An list of the enabled nd6 options or nil if error + */ +static int +l_ifconfig_get_nd6(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + struct in6_ndireq nd; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_get_nd6(h, name, &nd) != 0) + lua_pushnil(L); + else + push_flags_array(L, nd.ndi.flags, ND6BITS); + + return (1); +} + +/** Set interface metric + * @param name The name of an interface + * @param metric A metric to use + * @return true if success, false if error + */ +static int +l_ifconfig_set_metric(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + ptrdiff_t metric; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + luaL_checkinteger(L, 3); + + name = lua_tostring(L, 2); + metric = lua_tointeger(L, 3); + luaL_argcheck(L, INT_MAX < metric || metric < INT_MIN, 3, + "metric out of range"); + if (ifconfig_set_metric(h, name, metric) != 0) + lua_pushboolean(L, false); + else + lua_pushboolean(L, true); + + return (1); +} + +/** Get interface metric + * @param name The name of an interface + * @return metric of the interface or nil if error + */ +static int +l_ifconfig_get_metric(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + int metric; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_get_metric(h, name, &metric) != 0) + lua_pushnil(L); + else + lua_pushinteger(L, metric); + + return (1); +} + +/** Set interface capabilities + * @param name The name of an interface + * @param caps An array of the capabilities to be enabled on the interface + * (capabilities not listed will be disabled) + * @return true if success, false if error + */ +static int +l_ifconfig_set_capability(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + int capability; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + luaL_checktype(L, 3, LUA_TTABLE); + + name = lua_tostring(L, 2); + capability = pop_flags_array(L, 3, IFCAPBITS); + if (ifconfig_set_capability(h, name, capability) != 0) + lua_pushboolean(L, false); + else + lua_pushboolean(L, true); + + return (1); +} + +/** Get interface capabilities + * @param name The name of an interface + * @return A table of the enabled/supported capabilites or nil if error + */ +static int +l_ifconfig_get_capability(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + struct ifconfig_capabilities capability; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_get_capability(h, name, &capability) != 0) { + lua_pushnil(L); + return (1); + } + + lua_newtable(L); + + lua_pushstring(L, "options"); + push_flags_array(L, capability.curcap, IFCAPBITS); + lua_rawset(L, 3); + + lua_pushstring(L, "capabilities"); + push_flags_array(L, capability.reqcap, IFCAPBITS); + lua_rawset(L, 3); + + return (1); +} + +/** Get interface groups list + * @param name The name of an interface + * @return A table of the groups containing the interface or nil if error + */ +static int +l_ifconfig_get_groups(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + struct ifgroupreq ifgr; + struct ifg_req *ifg; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_get_groups(h, name, &ifgr) != 0) { + lua_pushnil(L); + return (1); + } + + lua_newtable(L); + + for (ifg = ifgr.ifgr_groups; + ifg != NULL && ifgr.ifgr_len >= sizeof *ifg; + ++ifg, ifgr.ifgr_len -= sizeof *ifg) { + if (strcmp(ifg->ifgrq_group, "all") == 0) + continue; + lua_pushstring(L, ifg->ifgrq_group); + lua_rawseti(L, 3, lua_rawlen(L, 3) + 1); + } + + return (1); +} + +/** Get interface status + * @param name The name of an interface + * @return Interface status as a string or nil if error + */ +static int +l_ifconfig_get_status(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + struct ifstat status; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_get_ifstatus(h, name, &status) != 0) + lua_pushnil(L); + else + lua_pushstring(L, status.ascii); + + return (1); +} + +/** Get interface media info + * @param name The name of an interface + * @return A table describing the interface media or nil if error + */ +static int +l_ifconfig_get_media(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + struct ifmediareq *ifmr; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_media_get_mediareq(h, name, &ifmr) != 0) { + lua_pushnil(L); + return (1); + } + + lua_newtable(L); + + push_media_info(L, ifmr->ifm_current); + lua_setfield(L, 3, "current"); + + push_media_info(L, ifmr->ifm_active); + lua_setfield(L, 3, "active"); + + lua_newtable(L); + for (size_t i = 0; i < ifmr->ifm_count; ++i) { + push_media_info(L, ifmr->ifm_ulist[i]); + lua_rawseti(L, 4, lua_rawlen(L, 4) + 1); + } + lua_setfield(L, 3, "supported"); + + lua_pushstring(L, ifconfig_media_get_status(ifmr)); + lua_setfield(L, 3, "status"); + + free(ifmr); + + return (1); +} + +/** Get interface carp info + * @param name + * @return A table describing the interface carp vhids or nil if error + */ +static int +l_ifconfig_get_carp(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + struct carpreq carpr[CARP_MAXVHID]; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_carp_get_info(h, name, carpr, nitems(carpr)) != 0) { + lua_pushnil(L); + return (1); + } + + lua_newtable(L); + + for (size_t i = 0; i < carpr[0].carpr_count; ++i) { + lua_newtable(L); + + lua_pushstring(L, carp_states[carpr[i].carpr_state]); + lua_setfield(L, 4, "state"); + + lua_pushinteger(L, carpr[i].carpr_vhid); + lua_setfield(L, 4, "vhid"); + + lua_pushinteger(L, carpr[i].carpr_advbase); + lua_setfield(L, 4, "advbase"); + + lua_pushinteger(L, carpr[i].carpr_advskew); + lua_setfield(L, 4, "advskew"); + + lua_rawseti(L, 3, lua_rawlen(L, 3) + 1); + } + + return (1); +} + +/** Get address info + * @param addr An ifaddrs userdata object + * @return Table describing the address or nil if error + */ +static int +l_ifconfig_addr_info(lua_State *L) +{ + ifconfig_handle_t *h; + struct ifaddrs *ifa; + + h = l_ifconfig_checkhandle(L, 1); + ifa = l_ifconfig_checkifaddrs(L, 2); + + switch (ifa->ifa_addr->sa_family) { + case AF_INET: + push_inet4_addrinfo(L, h, ifa); + break; + case AF_INET6: + push_inet6_addrinfo(L, h, ifa); + break; + case AF_LINK: + push_link_addrinfo(L, h, ifa); + break; + case AF_LOCAL: + push_local_addrinfo(L, h, ifa); + break; + case AF_UNSPEC: + default: + lua_pushnil(L); + break; + } + + return (1); +} + +/** Get lagg interface status + * @param name The name of a lagg interface + * @return Table describing the status of the lagg or nil if error + */ +static int +l_ifconfig_get_lagg_status(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + struct ifconfig_lagg_status *lagg_status; + const char *protoname = ""; + int index; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_lagg_get_lagg_status(h, name, &lagg_status) != 0) { + lua_pushnil(L); + return (1); + } + + lua_newtable(L); + index = lua_gettop(L); + + for (size_t i = 0; i < nitems(lagg_protos); ++i) { + if (lagg_status->ra->ra_proto == lagg_protos[i].lpr_proto) { + protoname = lagg_protos[i].lpr_name; + break; + } + } + lua_pushstring(L, protoname); + lua_setfield(L, index, "laggproto"); + + push_flags_array(L, lagg_status->rf->rf_flags & LAGG_F_HASHMASK, + LAGGHASHBITS); + lua_setfield(L, index, "lagghash"); + + lua_newtable(L); + ++index; + + push_flags_array(L, lagg_status->ro->ro_opts, LAGG_OPT_BITS); + lua_setfield(L, index, "flags"); + + lua_pushinteger(L, lagg_status->ro->ro_flowid_shift); + lua_setfield(L, index, "flowid_shift"); + + if (lagg_status->ra->ra_proto == LAGG_PROTO_ROUNDROBIN) { + lua_pushinteger(L, lagg_status->ro->ro_bkt); + lua_setfield(L, index, "rr_limit"); + } + + --index; + lua_setfield(L, index, "options"); + + lua_newtable(L); + ++index; + + lua_pushinteger(L, lagg_status->ro->ro_active); + lua_setfield(L, index, "active"); + + lua_pushinteger(L, lagg_status->ro->ro_flapping); + lua_setfield(L, index, "flapping"); + + --index; + lua_setfield(L, index, "stats"); + + lua_newtable(L); + ++index; + + for (size_t i = 0; i < lagg_status->ra->ra_ports; ++i) { + struct lagg_reqport *lagg_port = &lagg_status->ra->ra_port[i]; + + lua_newtable(L); + ++index; + + lua_pushstring(L, lagg_port->rp_portname); + lua_setfield(L, index, "laggport"); + + push_flags_array(L, lagg_port->rp_flags, LAGG_PORT_BITS); + lua_setfield(L, index, "flags"); + + if (lagg_status->ra->ra_proto == LAGG_PROTO_LACP) { + struct lacp_opreq *lacp_port = (struct lacp_opreq *) + &lagg_port->rp_lacpreq; + + push_flags_array(L, lacp_port->actor_state, + LACP_STATE_BITS); + lua_setfield(L, index, "lacp_state"); + } + + --index; + lua_rawseti(L, index, lua_rawlen(L, index) + 1); + } + + --index; + lua_setfield(L, index, "ports"); + + ifconfig_lagg_free_lagg_status(lagg_status); + + return (1); +} + +/** Get the parent laggdev of a laggport interface + * @param name The name of a laggport interface + * @return Parent laggdev name or nil if error + */ +static int +l_ifconfig_get_laggport_laggdev(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + struct lagg_reqport lagg_port; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_lagg_get_laggport_status(h, name, &lagg_port) != 0) + lua_pushnil(L); + else + lua_pushstring(L, lagg_port.rp_ifname); + + return (1); +} + +/** Destroy an interface + * @param name The name of an interface + * @return true if success, false if error + */ +static int +l_ifconfig_destroy(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_destroy_interface(h, name) != 0) + lua_pushboolean(L, false); + else + lua_pushboolean(L, true); + + return (1); +} + +/** Create an interface + * @param name The name of an interface + * @return Created interface name or nil if error + */ +static int +l_ifconfig_create(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name; + char *ifname; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + + name = lua_tostring(L, 2); + if (ifconfig_create_interface(h, name, &ifname) != 0) + lua_pushnil(L); + else + lua_pushstring(L, ifname); + + return (1); +} + +/** Create a vlan interface + * @param name The name of a vlan interface to create + * @param vlandev The name of an interface to attach to + * @param vlantag A vlan number to use + * @return The name of the created interface or nil if error + */ +static int +l_ifconfig_create_vlan(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name, *vlandev; + char *ifname; + ptrdiff_t vlantag; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + luaL_checkstring(L, 3); + luaL_checkinteger(L, 4); + + name = lua_tostring(L, 2); + vlandev = lua_tostring(L, 3); + vlantag = lua_tointeger(L, 4); + luaL_argcheck(L, 0 <= vlantag && vlantag <= UINT16_MAX, 4, + "vlantag out of range"); + if (ifconfig_create_interface_vlan(h, name, &ifname, vlandev, vlantag) != 0) + lua_pushnil(L); + else + lua_pushstring(L, ifname); + + return (1); +} + +/** Set vlandev and vlantag for a vlan interface + * @param name The name of a vlan interface + * @param vlandev The name of the interface to attach to + * @param vlantag A vlan number to use + * @return true if success, false if error + */ +static int +l_ifconfig_set_vlantag(lua_State *L) +{ + ifconfig_handle_t *h; + const char *name, *vlandev; + ptrdiff_t vlantag; + + h = l_ifconfig_checkhandle(L, 1); + luaL_checkstring(L, 2); + luaL_checkstring(L, 3); + luaL_checkinteger(L, 4); + + name = lua_tostring(L, 2); + vlandev = lua_tostring(L, 3); + vlantag = lua_tointeger(L, 4); + luaL_argcheck(L, 0 <= vlantag && vlantag <= UINT16_MAX, 4, + "vlantag out of range"); + if (ifconfig_set_vlantag(h, name, vlandev, vlantag) != 0) + lua_pushboolean(L, false); + else + lua_pushboolean(L, true); + + return (1); +} + +static const struct luaL_Reg l_ifconfig_handle[] = { + {"close", l_ifconfig_handle_close}, + {"error", l_ifconfig_handle_error}, + {"foreach_iface", l_ifconfig_foreach_iface}, + {"foreach_ifaddr", l_ifconfig_foreach_ifaddr}, + {"get_description", l_ifconfig_get_description}, + {"set_description", l_ifconfig_set_description}, + {"unset_description", l_ifconfig_unset_description}, + {"set_name", l_ifconfig_set_name}, + {"get_orig_name", l_ifconfig_get_orig_name}, +#if 0 /* setfib not implemented in libifconfig? */ + {"set_fib", l_ifconfig_set_fib}, +#endif + {"get_fib", l_ifconfig_get_fib}, + {"set_mtu", l_ifconfig_set_mtu}, + {"get_mtu", l_ifconfig_get_mtu}, + {"get_nd6", l_ifconfig_get_nd6}, + {"set_metric", l_ifconfig_set_metric}, + {"get_metric", l_ifconfig_get_metric}, + {"set_capability", l_ifconfig_set_capability}, + {"get_capability", l_ifconfig_get_capability}, + {"get_groups", l_ifconfig_get_groups}, + {"get_status", l_ifconfig_get_status}, + {"get_media", l_ifconfig_get_media}, + {"get_carp", l_ifconfig_get_carp}, + {"addr_info", l_ifconfig_addr_info}, + {"get_lagg_status", l_ifconfig_get_lagg_status}, + {"get_laggport_laggdev", l_ifconfig_get_laggport_laggdev}, + {"destroy", l_ifconfig_destroy}, + {"create", l_ifconfig_create}, + {"create_vlan", l_ifconfig_create_vlan}, + {"set_vlantag", l_ifconfig_set_vlantag}, + {NULL, NULL} +}; + +static void +l_ifconfig_handle_meta(lua_State *L) +{ + luaL_newmetatable(L, IFCONFIG_HANDLE_META); + + /* metatable.__index = metatable */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + + /* Automatically close handle when garbage collected. */ + lua_pushcfunction(L, l_ifconfig_handle_close); + lua_setfield(L, -2, "__gc"); + + luaL_setfuncs(L, l_ifconfig_handle, 0); + + lua_pop(L, 1); +} + +/** Get interface name + * @return Name of the interface as a string + */ +static int +l_struct_ifaddrs_name(lua_State *L) +{ + struct ifaddrs *ifa; + + ifa = l_ifconfig_checkifaddrs(L, 1); + + lua_pushstring(L, ifa->ifa_name); + return (1); +} + +static const struct luaL_Reg l_struct_ifaddrs[] = { + {"name", l_struct_ifaddrs_name}, + {NULL, NULL} +}; + +static void +l_struct_ifaddrs_meta(lua_State *L) +{ + luaL_newmetatable(L, STRUCT_IFADDRS_META); + + /* metatable.__index = metatable */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + + luaL_setfuncs(L, l_struct_ifaddrs, 0); + + lua_pop(L, 1); +} + +static int +l_ifconfig_open(lua_State *L) +{ + ifconfig_handle_t *h, **hp; + + if ((h = ifconfig_open()) == NULL) { + lua_pushnil(L); + return (1); + } + + hp = lua_newuserdata(L, sizeof h); + assert(hp != NULL); + *hp = h; + luaL_getmetatable(L, IFCONFIG_HANDLE_META); + lua_setmetatable(L, -2); + return (1); +} + +static const struct luaL_Reg l_ifconfig[] = { + {"open", l_ifconfig_open}, + {NULL, NULL} +}; + +int +luaopen_ifconfig(lua_State *L) +{ + l_ifconfig_handle_meta(L); + l_struct_ifaddrs_meta(L); + + lua_newtable(L); + luaL_setfuncs(L, l_ifconfig, 0); + return (1); +}