phunix/minix/net/inet/rtinfo.c
David van Moolenbroek 764cd267a7 INET/LWIP: minimal net.route sysctl support
At a point not too far in the future, we will be switching from the
hardcoded MINIX3 implementation of the getifaddrs(3) libc routine to
the proper NetBSD implementation.  The latter uses the
net.route.rtable sysctl functionality to obtain its information.  In
order make the transition as painless as possible, this patch adds
basic support for that net.route.rtable functionality to INET and
LWIP, using the remote MIB (RMIB) facility.

Change-Id: I54f5cea7985f6606e317c73a5e6be3a5d07bc7dc
2016-06-18 12:47:30 +00:00

462 lines
12 KiB
C

/*
* Mock net.route sysctl(2) subtree implementation using RMIB. This code
* serves as a temporary bridge to allow libc to switch from the original,
* native MINIX3 getifaddrs(3) to the NetBSD getifaddrs(3). As such, it
* implements only a small subset of NetBSD's full net.route functionality,
* although also more than needed only to imitate the MINIX3 getifaddrs(3).
*/
#include "inet.h"
#include "generic/type.h"
#include "generic/buf.h"
#include "generic/event.h"
#include "generic/ip_int.h"
#include "osdep_eth.h"
#include "generic/eth_int.h"
#include <netinet/in.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/route.h>
#include <sys/sysctl.h>
#include <minix/rmib.h>
#include <assert.h>
/* Max. number of bytes for a full sockaddr_dl structure, including data. */
#define SDL_BUFSIZE (sizeof(struct sockaddr_dl) + 32)
static const char padbuf[RT_ROUNDUP(0)] = { 0 };
/*
* Copy out a vector of data to the sysctl(2) caller. TODO: decide what to do
* with this. We could implement this as a vectored-safecopy operation in
* RMIB. We could also copy everything into a single buffer first. The
* current implementation is probably the worst among the possibilities.
*/
static ssize_t
rmib_vcopyout(struct rmib_oldp * oldp, size_t off, const iovec_t * iov,
unsigned int iovcnt)
{
unsigned int i;
ssize_t r, len;
len = 0;
for (i = 0; i < iovcnt; i++) {
if ((r = rmib_copyout(oldp, off + len,
(const void *)iov[i].iov_addr, iov[i].iov_size)) < 0)
return r;
len += r;
}
return len;
}
/*
* Compute the length for, and possibly copy out, an interface information or
* interface address record with an associated set of zero or more routing
* table addresses. The addresses are padded as necessary. Store the full
* record length and the address bitmap before copying out the entire record.
*/
static ssize_t
copyout_rta(void * hdr, size_t size, u_short * msglen, int * addrs,
void * rta_map[RTAX_MAX], size_t rta_len[RTAX_MAX],
struct rmib_oldp * oldp, ssize_t off)
{
iovec_t iov[1 + RTAX_MAX * 2];
size_t len, total, padlen;
unsigned int i, iovcnt;
int mask;
iovcnt = 0;
iov[iovcnt].iov_addr = (vir_bytes)hdr;
iov[iovcnt++].iov_size = size;
total = size;
mask = 0;
/*
* Any addresses in the given map should be stored in the numbering
* order of the map.
*/
for (i = 0; i < RTAX_MAX; i++) {
if (rta_map[i] == NULL)
continue;
assert(iovcnt < __arraycount(iov));
iov[iovcnt].iov_addr = (vir_bytes)rta_map[i];
iov[iovcnt++].iov_size = len = rta_len[i];
padlen = RT_ROUNDUP(len) - len;
if (padlen > 0) {
assert(iovcnt < __arraycount(iov));
iov[iovcnt].iov_addr = (vir_bytes)padbuf;
iov[iovcnt++].iov_size = padlen;
}
total += len + padlen;
mask |= (1 << i);
}
/* If only the length was requested, return it now. */
if (oldp == NULL)
return total;
/*
* Casting 'hdr' would violate C99 strict aliasing rules, so store the
* computed header values through direct pointers. Bah.
*/
*msglen = total;
*addrs = mask;
return rmib_vcopyout(oldp, off, iov, iovcnt);
}
/*
* Given an INET ip-layer datalink type, return a matching BSD interface type.
*/
static unsigned char
ipdl_to_ift(int ip_dl_type)
{
switch (ip_dl_type) {
case IPDL_ETH:
return IFT_ETHER;
case IPDL_PSIP:
return IFT_LOOP;
default:
return IFT_OTHER;
}
}
/*
* Compute the length for, and possibly generate, a sockaddr_dl structure for
* the given interface. The complication here is that the structure contains
* various field packed together dynamically, making it variable sized.
*/
static size_t
make_sdl(const ip_port_t * ip_port, int ndx, char * buf, size_t max)
{
const eth_port_t *eth_port;
struct sockaddr_dl sdl;
static char namebuf[8];
const void *addrptr;
size_t hdrlen, namelen, addrlen, padlen, len;
/* Normally the interface name would be pregenerated somewhere. */
snprintf(namebuf, sizeof(namebuf), "ip%u", ip_port->ip_port);
namelen = strlen(namebuf);
addrlen = 0;
if (ip_port->ip_dl_type == IPDL_ETH) {
eth_port = &eth_port_table[ip_port->ip_dl.dl_eth.de_port];
if (eth_port->etp_flags & EPF_GOT_ADDR) {
addrptr = &eth_port->etp_ethaddr;
addrlen = sizeof(eth_port->etp_ethaddr);
}
}
/*
* Compute the unpadded and padded length of the structure. We pad the
* structure ourselves here, even though the caller will otherwise pad
* it later, because it is easy to do so and saves on a vector element.
*/
hdrlen = offsetof(struct sockaddr_dl, sdl_data);
len = hdrlen + namelen + addrlen;
padlen = RT_ROUNDUP(len) - len;
assert(len + padlen <= max);
/* If we are asked not to generate the actual data, stop here. */
if (buf == NULL)
return len + padlen;
/*
* Fill the sockaddr_dl structure header. The C99 strict aliasing
* rules prevent us from filling 'buf' through a pointer structure
* directly.
*/
memset(&sdl, 0, hdrlen);
sdl.sdl_len = len;
sdl.sdl_family = AF_LINK;
sdl.sdl_index = ndx;
sdl.sdl_type = ipdl_to_ift(ip_port->ip_dl_type);
sdl.sdl_nlen = namelen;
sdl.sdl_alen = addrlen;
sdl.sdl_slen = 0;
/*
* Generate the full sockaddr_dl structure in the given buffer. These
* memory sizes are typically small, so the extra memory copies are not
* too expensive. The advantage of generating a single sockaddr_dl
* structure buffer is that we can use copyout_rta() on it.
*/
memcpy(buf, &sdl, hdrlen);
if (namelen > 0)
memcpy(&buf[hdrlen], namebuf, namelen);
if (addrlen > 0)
memcpy(&buf[hdrlen + namelen], addrptr, addrlen);
if (padlen > 0)
memset(&buf[len], 0, padlen);
return len + padlen;
}
/*
* Compute the length for, and possibly generate, an interface information
* record for the given interface.
*/
static ssize_t
gen_ifm(const ip_port_t * ip_port, int ndx, int is_up, struct rmib_oldp * oldp,
ssize_t off)
{
struct if_msghdr ifm;
char buf[SDL_BUFSIZE];
void *rta_map[RTAX_MAX];
size_t rta_len[RTAX_MAX], size;
if (oldp != NULL) {
memset(&ifm, 0, sizeof(ifm));
ifm.ifm_version = RTM_VERSION;
ifm.ifm_type = RTM_IFINFO;
ifm.ifm_flags = (is_up) ? (IFF_UP | IFF_RUNNING) : 0;
ifm.ifm_index = ndx;
ifm.ifm_data.ifi_type = ipdl_to_ift(ip_port->ip_dl_type);
if (ifm.ifm_data.ifi_type == IFT_LOOP)
ifm.ifm_flags |= IFF_LOOPBACK;
/* TODO: other ifm_flags, other ifm_data fields, etc. */
}
/*
* Note that we add padding even in this case, to ensure that the
* following structures are properly aligned as well.
*/
size = make_sdl(ip_port, ndx, (oldp != NULL) ? buf : NULL,
sizeof(buf));
memset(rta_map, 0, sizeof(rta_map));
rta_map[RTAX_IFP] = buf;
rta_len[RTAX_IFP] = size;
return copyout_rta(&ifm, sizeof(ifm), &ifm.ifm_msglen, &ifm.ifm_addrs,
rta_map, rta_len, oldp, off);
}
/*
* Compute the length for, and possibly generate, an AF_LINK-family interface
* address record.
*/
static ssize_t
gen_ifam_dl(const ip_port_t * ip_port, int ndx, int is_up,
struct rmib_oldp * oldp, ssize_t off)
{
struct ifa_msghdr ifam;
char buf[SDL_BUFSIZE];
void *rta_map[RTAX_MAX];
size_t rta_len[RTAX_MAX], size;
if (oldp != NULL) {
memset(&ifam, 0, sizeof(ifam));
ifam.ifam_version = RTM_VERSION;
ifam.ifam_type = RTM_NEWADDR;
ifam.ifam_index = ndx;
ifam.ifam_metric = 0; /* unknown and irrelevant */
}
size = make_sdl(ip_port, ndx, (oldp != NULL) ? buf : NULL,
sizeof(buf));
/*
* We do not generate a netmask. NetBSD seems to generate a netmask
* with all-one bits for the number of bytes equal to the name length,
* for reasons unknown to me. If we did the same, we would end up with
* a conflict on the static 'namebuf' buffer.
*/
memset(rta_map, 0, sizeof(rta_map));
rta_map[RTAX_IFA] = buf;
rta_len[RTAX_IFA] = size;
return copyout_rta(&ifam, sizeof(ifam), &ifam.ifam_msglen,
&ifam.ifam_addrs, rta_map, rta_len, oldp, off);
}
/*
* Compute the length for, and possibly generate, an AF_INET-family interface
* address record.
*/
static ssize_t
gen_ifam_inet(const ip_port_t * ip_port, int ndx, int is_up,
struct rmib_oldp * oldp, ssize_t off)
{
struct ifa_msghdr ifam;
struct sockaddr_in ipaddr, netmask;
void *rta_map[RTAX_MAX];
size_t rta_len[RTAX_MAX];
if (oldp != NULL) {
memset(&ifam, 0, sizeof(ifam));
ifam.ifam_msglen = sizeof(ifam);
ifam.ifam_version = RTM_VERSION;
ifam.ifam_type = RTM_NEWADDR;
ifam.ifam_addrs = 0;
ifam.ifam_index = ndx;
ifam.ifam_metric = 0; /* unknown and irrelevant */
}
memset(rta_map, 0, sizeof(rta_map));
if (ip_port->ip_flags & IPF_IPADDRSET) {
if (oldp != NULL) {
memset(&ipaddr, 0, sizeof(ipaddr));
ipaddr.sin_family = AF_INET;
ipaddr.sin_len = sizeof(ipaddr);
ipaddr.sin_addr.s_addr = ip_port->ip_ipaddr;
}
rta_map[RTAX_IFA] = &ipaddr;
rta_len[RTAX_IFA] = sizeof(ipaddr);
}
if (ip_port->ip_flags & IPF_NETMASKSET) {
/*
* TODO: BSD goes through the trouble of compressing the
* netmask for some reason. We need to figure out if
* compression is actually required by any part of userland.
*/
if (oldp != NULL) {
memset(&netmask, 0, sizeof(netmask));
netmask.sin_family = AF_INET;
netmask.sin_len = sizeof(netmask);
netmask.sin_addr.s_addr = ip_port->ip_subnetmask;
}
rta_map[RTAX_NETMASK] = &netmask;
rta_len[RTAX_NETMASK] = sizeof(netmask);
}
return copyout_rta(&ifam, sizeof(ifam), &ifam.ifam_msglen,
&ifam.ifam_addrs, rta_map, rta_len, oldp, off);
}
/*
* Compute the size needed for, and optionally copy out, the interface and
* address information for the given interface.
*/
static ssize_t
do_one_if(const ip_port_t * ip_port, int ndx, struct rmib_oldp * oldp,
ssize_t off, int filter)
{
ssize_t r, len;
int is_up;
/*
* If the interface is not configured, we mark it as down and do not
* provide IP address information.
*/
is_up = (ip_port->ip_flags & IPF_IPADDRSET);
len = 0;
/* There is always a full interface information record. */
if ((r = gen_ifm(ip_port, ndx, is_up, oldp, off)) < 0)
return r;
len += r;
/* If not filtered, there is a datalink address record. */
if (filter == 0 || filter == AF_LINK) {
if ((r = gen_ifam_dl(ip_port, ndx, is_up, oldp,
off + len)) < 0)
return r;
len += r;
}
/* If configured and not filtered, there is an IPv4 address record. */
if (is_up && (filter == 0 || filter == AF_INET)) {
if ((r = gen_ifam_inet(ip_port, ndx, is_up, oldp,
off + len)) < 0)
return r;
len += r;
}
/*
* Whether or not anything was copied out, upon success we return the
* full length of the data.
*/
return len;
}
/*
* Remote MIB implementation of CTL_NET PF_ROUTE 0. This function handles all
* queries on the "net.route.rtable" sysctl(2) node.
*/
static ssize_t
net_route_rtable(struct rmib_call * call, struct rmib_node * node __unused,
struct rmib_oldp * oldp, struct rmib_newp * newp __unused)
{
const ip_port_t *ip_port;
ssize_t r, off;
int i, filter, ndx;
if (call->call_namelen != 3)
return EINVAL;
/* We only support listing interfaces for now. */
if (call->call_name[1] != NET_RT_IFLIST)
return EOPNOTSUPP;
filter = call->call_name[0];
ndx = call->call_name[2];
off = 0;
for (i = 0, ip_port = ip_port_table; i < ip_conf_nr; i++, ip_port++) {
if (!(ip_port->ip_flags & IPF_CONFIGURED))
continue;
/*
* If information about a specific interface index is requested
* then skip all other entries. Interface indices must be
* nonzero, so we shift the numbers by one. We can avoid going
* through the loop altogether here, but getifaddrs(3) does not
* query specific interfaces anyway.
*/
if (ndx != 0 && ndx != ip_port->ip_port + 1)
continue;
/* Avoid generating results that are never copied out. */
if (oldp != NULL && !rmib_inrange(oldp, off))
oldp = NULL;
if ((r = do_one_if(ip_port, ip_port->ip_port + 1, oldp, off,
filter)) < 0)
return r;
off += r;
}
return off;
}
/* The CTL_NET PF_ROUTE subtree. */
static struct rmib_node net_route_table[] = {
[0] = RMIB_FUNC(RMIB_RO | CTLTYPE_NODE, 0, net_route_rtable,
"rtable", "Routing table information")
};
/* The CTL_NET PF_ROUTE node. */
static struct rmib_node net_route_node =
RMIB_NODE(RMIB_RO, net_route_table, "route", "PF_ROUTE information");
/*
* Register the net.route RMIB subtree with the MIB service. Since inet does
* not support clean shutdowns, there is no matching cleanup function.
*/
void
rtinfo_init(void)
{
const int mib[] = { CTL_NET, PF_ROUTE };
int r;
if ((r = rmib_register(mib, __arraycount(mib), &net_route_node)) != OK)
panic("unable to register remote MIB tree: %d", r);
}