/* SPDX-License-Identifier: BSD-2-Clause */
/*
* Solaris interface driver for dhcpcd
* Copyright (c) 2016-2021 Roy Marples <roy@marples.name>
* All rights reserved
* 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 AUTHOR 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 AUTHOR 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.
*/
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <libdlpi.h>
#include <kstat.h>
#include <stddef.h>
#include <stdlib.h>
#include <stropts.h>
#include <string.h>
#include <unistd.h>
#include <inet/ip.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <netinet/if_ether.h>
#include <netinet/udp.h>
#include <sys/ioctl.h>
#include <sys/mac.h>
#include <sys/pfmod.h>
#include <sys/tihdr.h>
#include <sys/utsname.h>
/* Private libsocket interface we can hook into to get
* a better getifaddrs(3).
* From libsocket_priv.h, which is not always distributed so is here. */
extern int getallifaddrs(sa_family_t, struct ifaddrs **, int64_t);
#include "config.h"
#include "bpf.h"
#include "common.h"
#include "dhcp.h"
#include "if.h"
#include "if-options.h"
#include "ipv4.h"
#include "ipv6.h"
#include "ipv6nd.h"
#include "logerr.h"
#include "route.h"
#include "sa.h"
#ifndef ARP_MOD_NAME
# define ARP_MOD_NAME "arp"
#endif
#ifndef RT_ROUNDUP
#define RT_ROUNDUP(a) \
((a) > 0 ? (1 + (((a) - 1) | (sizeof(int32_t) - 1))) : sizeof(int32_t))
#define RT_ADVANCE(x, n) ((x) += RT_ROUNDUP(sa_len((n))))
#endif
#define COPYOUT(sin, sa) do { \
if ((sa) && ((sa)->sa_family == AF_INET)) \
(sin) = ((const struct sockaddr_in *)(const void *) \
(sa))->sin_addr; \
} while (0)
#define COPYOUT6(sin, sa) do { \
if ((sa) && ((sa)->sa_family == AF_INET6)) \
(sin) = ((const struct sockaddr_in6 *)(const void *) \
(sa))->sin6_addr; \
} while (0)
#define COPYSA(dst, src) memcpy((dst), (src), sa_len((src)))
struct priv {
#ifdef INET6
int pf_inet6_fd;
#endif
};
struct rtm
{
struct rt_msghdr hdr;
char buffer[sizeof(struct sockaddr_storage) * RTAX_MAX];
};
static int if_plumb(int, const struct dhcpcd_ctx *, int, const char *);
int
os_init(void)
{
return 0;
}
int
if_init(struct interface *ifp)
{
#ifdef INET
if (if_plumb(RTM_NEWADDR, ifp->ctx, AF_INET, ifp->name) == -1 &&
errno != EEXIST)
return -1;
#endif
#ifdef INET6
if (if_plumb(RTM_NEWADDR, ifp->ctx, AF_INET6, ifp->name) == -1 &&
errno != EEXIST)
return -1;
#endif
if (ifp->index == 0)
ifp->index = if_nametoindex(ifp->name);
return 0;
}
int
if_conf(__unused struct interface *ifp)
{
return 0;
}
int
if_opensockets_os(struct dhcpcd_ctx *ctx)
{
struct priv *priv;
int n;
if ((priv = malloc(sizeof(*priv))) == NULL)
return -1;
ctx->priv = priv;
#ifdef INET6
priv->pf_inet6_fd = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
/* Don't return an error so we at least work on kernels witout INET6
* even though we expect INET6 support.
* We will fail noisily elsewhere anyway. */
#endif
ctx->link_fd = socket(PF_ROUTE,
SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
if (ctx->link_fd == -1) {
free(ctx->priv);
return -1;
}
/* Ignore our own route(4) messages.
* Sadly there is no way of doing this for route(4) messages
* generated from addresses we add/delete. */
n = 0;
if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_USELOOPBACK,
&n, sizeof(n)) == -1)
logerr("%s: SO_USELOOPBACK", __func__);
return 0;
}
void
if_closesockets_os(struct dhcpcd_ctx *ctx)
{
#ifdef INET6
struct priv *priv;
priv = (struct priv *)ctx->priv;
if (priv->pf_inet6_fd != -1)
close(priv->pf_inet6_fd);
#endif
/* each interface should have closed itself */
free(ctx->priv);
}
int
if_setmac(struct interface *ifp, void *mac, uint8_t maclen)
{
errno = ENOTSUP;
return -1;
}
int
if_carrier(struct interface *ifp, __unused const void *ifadata)
{
kstat_ctl_t *kcp;
kstat_t *ksp;
kstat_named_t *knp;
link_state_t linkstate;
if (if_getflags(ifp) == -1)
return LINK_UNKNOWN;
kcp = kstat_open();
if (kcp == NULL)
goto err;
ksp = kstat_lookup(kcp, UNCONST("link"), 0, ifp->name);
if (ksp == NULL)
goto err;
if (kstat_read(kcp, ksp, NULL) == -1)
goto err;
knp = kstat_data_lookup(ksp, UNCONST("link_state"));
if (knp == NULL)
goto err;
if (knp->data_type != KSTAT_DATA_UINT32)
goto err;
linkstate = (link_state_t)knp->value.ui32;
kstat_close(kcp);
switch (linkstate) {
case LINK_STATE_UP:
ifp->flags |= IFF_UP;
return LINK_UP;
case LINK_STATE_DOWN:
return LINK_DOWN;
default:
return LINK_UNKNOWN;
}
err:
if (kcp != NULL)
kstat_close(kcp);
return LINK_UNKNOWN;
}
bool
if_roaming(__unused struct interface *ifp)
{
return false;
}
int
if_mtu_os(const struct interface *ifp)
{
dlpi_handle_t dh;
dlpi_info_t dlinfo;
int mtu;
if (dlpi_open(ifp->name, &dh, 0) != DLPI_SUCCESS)
return -1;
if (dlpi_info(dh, &dlinfo, 0) == DLPI_SUCCESS)
mtu = dlinfo.di_max_sdu;
else
mtu = -1;
dlpi_close(dh);
return mtu;
}
int
if_getssid(__unused struct interface *ifp)
{
errno = ENOTSUP;
return -1;
}
/* XXX work out TAP interfaces? */
bool
if_ignore(__unused struct dhcpcd_ctx *ctx, __unused const char *ifname)
{
return false;
}
unsigned short
if_vlanid(__unused const struct interface *ifp)
{
return 0;
}
int
if_vimaster(__unused struct dhcpcd_ctx *ctx, __unused const char *ifname)
{
return 0;
}
int
if_machinearch(__unused char *str, __unused size_t len)
{
/* There is no extra data really.
* isainfo -v does return amd64, but also i386. */
return 0;
}
struct linkwalk {
struct ifaddrs *lw_ifa;
int lw_error;
};
static boolean_t
if_newaddr(const char *ifname, void *arg)
{
struct linkwalk *lw = arg;
int error;
struct ifaddrs *ifa;
dlpi_handle_t dh;
dlpi_info_t dlinfo;
uint8_t pa[DLPI_PHYSADDR_MAX];
size_t pa_len;
struct sockaddr_dl *sdl;
ifa = NULL;
error = dlpi_open(ifname, &dh, 0);
if (error == DLPI_ENOLINK) /* Just vanished or in global zone */
return B_FALSE;
if (error != DLPI_SUCCESS)
goto failed1;
if (dlpi_info(dh, &dlinfo, 0) != DLPI_SUCCESS)
goto failed;
/* For some reason, dlpi_info won't return the
* physical address, it's all zero's.
* So cal dlpi_get_physaddr. */
pa_len = DLPI_PHYSADDR_MAX;
if (dlpi_get_physaddr(dh, DL_CURR_PHYS_ADDR,
pa, &pa_len) != DLPI_SUCCESS)
goto failed;
if ((ifa = calloc(1, sizeof(*ifa))) == NULL)
goto failed;
if ((ifa->ifa_name = strdup(ifname)) == NULL)
goto failed;
if ((sdl = calloc(1, sizeof(*sdl))) == NULL)
goto failed;
ifa->ifa_addr = (struct sockaddr *)sdl;
sdl->sdl_index = if_nametoindex(ifname);
sdl->sdl_family = AF_LINK;
switch (dlinfo.di_mactype) {
case DL_ETHER:
sdl->sdl_type = IFT_ETHER;
break;
case DL_IB:
sdl->sdl_type = IFT_IB;
break;
default:
sdl->sdl_type = IFT_OTHER;
break;
}
sdl->sdl_alen = pa_len;
memcpy(sdl->sdl_data, pa, pa_len);
ifa->ifa_next = lw->lw_ifa;
lw->lw_ifa = ifa;
dlpi_close(dh);
return B_FALSE;
failed:
dlpi_close(dh);
if (ifa != NULL) {
free(ifa->ifa_name);
free(ifa->ifa_addr);
free(ifa);
}
failed1:
lw->lw_error = errno;
return B_TRUE;
}
/* Creates an empty sockaddr_dl for lo0. */
static struct ifaddrs *
if_ifa_lo0(void)
{
struct ifaddrs *ifa;
struct sockaddr_dl *sdl;
if ((ifa = calloc(1, sizeof(*ifa))) == NULL)
return NULL;
if ((sdl = calloc(1, sizeof(*sdl))) == NULL) {
free(ifa);
return NULL;
}
if ((ifa->ifa_name = strdup("lo0")) == NULL) {
free(ifa);
free(sdl);
return NULL;
}
ifa->ifa_addr = (struct sockaddr *)sdl;
ifa->ifa_flags = IFF_LOOPBACK;
sdl->sdl_family = AF_LINK;
sdl->sdl_index = if_nametoindex("lo0");
return ifa;
}
/* getifaddrs(3) does not support AF_LINK, strips aliases and won't
* report addresses that are not UP.
* As such it's just totally useless, so we need to roll our own. */
int
if_getifaddrs(struct ifaddrs **ifap)
{
struct linkwalk lw;
struct ifaddrs *ifa;
/* Private libc function which we should not have to call
* to get non UP addresses. */
if (getallifaddrs(AF_UNSPEC, &lw.lw_ifa, 0) == -1)
return -1;
/* Start with some AF_LINK addresses. */
lw.lw_error = 0;
dlpi_walk(if_newaddr, &lw, 0);
if (lw.lw_error != 0) {
freeifaddrs(lw.lw_ifa);
errno = lw.lw_error;
return -1;
}
/* lo0 doesn't appear in dlpi_walk, so fudge it. */
if ((ifa = if_ifa_lo0()) == NULL) {
freeifaddrs(lw.lw_ifa);
return -1;
}
ifa->ifa_next = lw.lw_ifa;
*ifap = ifa;
return 0;
}
static void
if_linkaddr(struct sockaddr_dl *sdl, const struct interface *ifp)
{
memset(sdl, 0, sizeof(*sdl));
sdl->sdl_family = AF_LINK;
sdl->sdl_nlen = sdl->sdl_alen = sdl->sdl_slen = 0;
sdl->sdl_index = (unsigned short)ifp->index;
}
static int
get_addrs(int type, const void *data, size_t data_len,
const struct sockaddr **sa)
{
const char *cp, *ep;
int i;
cp = data;
ep = cp + data_len;
for (i = 0; i < RTAX_MAX; i++) {
if (type & (1 << i)) {
if (cp >= ep) {
errno = EINVAL;
return -1;
}
sa[i] = (const struct sockaddr *)cp;
RT_ADVANCE(cp, sa[i]);
} else
sa[i] = NULL;
}
return 0;
}
static struct interface *
if_findsdl(struct dhcpcd_ctx *ctx, const struct sockaddr_dl *sdl)
{
if (sdl->sdl_index)
return if_findindex(ctx->ifaces, sdl->sdl_index);
if (sdl->sdl_nlen) {
char ifname[IF_NAMESIZE];
memcpy(ifname, sdl->sdl_data, sdl->sdl_nlen);
ifname[sdl->sdl_nlen] = '\0';
return if_find(ctx->ifaces, ifname);
}
if (sdl->sdl_alen) {
struct interface *ifp;
TAILQ_FOREACH(ifp, ctx->ifaces, next) {
if (ifp->hwlen == sdl->sdl_alen &&
memcmp(ifp->hwaddr,
sdl->sdl_data, sdl->sdl_alen) == 0)
return ifp;
}
}
errno = ENOENT;
return NULL;
}
static struct interface *
if_findsa(struct dhcpcd_ctx *ctx, const struct sockaddr *sa)
{
if (sa == NULL) {
errno = EINVAL;
return NULL;
}
switch (sa->sa_family) {
case AF_LINK:
{
const struct sockaddr_dl *sdl;
sdl = (const void *)sa;
return if_findsdl(ctx, sdl);
}
#ifdef INET
case AF_INET:
{
const struct sockaddr_in *sin;
struct ipv4_addr *ia;
sin = (const void *)sa;
if ((ia = ipv4_findmaskaddr(ctx, &sin->sin_addr)))
return ia->iface;
if ((ia = ipv4_findmaskbrd(ctx, &sin->sin_addr)))
return ia->iface;
break;
}
#endif
#ifdef INET6
case AF_INET6:
{
const struct sockaddr_in6 *sin;
struct ipv6_addr *ia;
sin = (const void *)sa;
if ((ia = ipv6_findmaskaddr(ctx, &sin->sin6_addr)))
return ia->iface;
break;
}
#endif
default:
errno = EAFNOSUPPORT;
return NULL;
}
errno = ENOENT;
return NULL;
}
static void
if_route0(struct dhcpcd_ctx *ctx, struct rtm *rtmsg,
unsigned char cmd, const struct rt *rt)
{
struct rt_msghdr *rtm;
char *bp = rtmsg->buffer;
socklen_t sl;
bool gateway_unspec;
/* WARNING: Solaris will not allow you to delete RTF_KERNEL routes.
* This includes subnet/prefix routes. */
#define ADDSA(sa) do { \
sl = sa_len((sa)); \
memcpy(bp, (sa), sl); \
bp += RT_ROUNDUP(sl); \
} while (/* CONSTCOND */ 0)
memset(rtmsg, 0, sizeof(*rtmsg));
rtm = &rtmsg->hdr;
rtm->rtm_version = RTM_VERSION;
rtm->rtm_type = cmd;
rtm->rtm_seq = ++ctx->seq;
rtm->rtm_flags = rt->rt_flags;
rtm->rtm_addrs = RTA_DST | RTA_GATEWAY;
gateway_unspec = sa_is_unspecified(&rt->rt_gateway);
if (cmd == RTM_ADD || cmd == RTM_CHANGE) {
bool netmask_bcast = sa_is_allones(&rt->rt_netmask);
rtm->rtm_flags |= RTF_UP;
if (!(rtm->rtm_flags & RTF_REJECT) &&
!sa_is_loopback(&rt->rt_gateway))
{
rtm->rtm_addrs |= RTA_IFP;
/* RTA_IFA is currently ignored by the kernel.
* RTA_SRC and RTF_SETSRC look like what we want,
* but they don't work with RTF_GATEWAY.
* We set RTA_IFA just in the hope that the
* kernel will one day support this. */
if (!sa_is_unspecified(&rt->rt_ifa))
rtm->rtm_addrs |= RTA_IFA;
}
if (netmask_bcast)
rtm->rtm_flags |= RTF_HOST;
else if (!gateway_unspec)
rtm->rtm_flags |= RTF_GATEWAY;
if (rt->rt_dflags & RTDF_STATIC)
rtm->rtm_flags |= RTF_STATIC;
if (rt->rt_mtu != 0) {
rtm->rtm_inits |= RTV_MTU;
rtm->rtm_rmx.rmx_mtu = rt->rt_mtu;
}
}
if (!(rtm->rtm_flags & RTF_HOST))
rtm->rtm_addrs |= RTA_NETMASK;
ADDSA(&rt->rt_dest);
if (gateway_unspec)
ADDSA(&rt->rt_ifa);
else
ADDSA(&rt->rt_gateway);
if (rtm->rtm_addrs & RTA_NETMASK)
ADDSA(&rt->rt_netmask);
if (rtm->rtm_addrs & RTA_IFP) {
struct sockaddr_dl sdl;
if_linkaddr(&sdl, rt->rt_ifp);
ADDSA((struct sockaddr *)&sdl);
}
if (rtm->rtm_addrs & RTA_IFA)
ADDSA(&rt->rt_ifa);
#if 0
if (rtm->rtm_addrs & RTA_SRC)
ADDSA(&rt->rt_ifa);
#endif
rtm->rtm_msglen = (unsigned short)(bp - (char *)rtm);
}
int
if_route(unsigned char cmd, const struct rt *rt)
{
struct rtm rtm;
struct dhcpcd_ctx *ctx = rt->rt_ifp->ctx;
if_route0(ctx, &rtm, cmd, rt);
if (write(ctx->link_fd, &rtm, rtm.hdr.rtm_msglen) == -1)
return -1;
return 0;
}
static int
if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, const struct rt_msghdr *rtm)
{
const struct sockaddr *rti_info[RTAX_MAX];
if (~rtm->rtm_addrs & RTA_DST) {
errno = EINVAL;
return -1;
}
if (get_addrs(rtm->rtm_addrs, (const char *)rtm + sizeof(*rtm),
rtm->rtm_msglen - sizeof(*rtm), rti_info) == -1)
return -1;
memset(rt, 0, sizeof(*rt));
rt->rt_flags = (unsigned int)rtm->rtm_flags;
COPYSA(&rt->rt_dest, rti_info[RTAX_DST]);
if (rtm->rtm_addrs & RTA_NETMASK)
COPYSA(&rt->rt_netmask, rti_info[RTAX_NETMASK]);
/* dhcpcd likes an unspecified gateway to indicate via the link.
* However we need to know if gateway was a link with an address. */
if (rtm->rtm_addrs & RTA_GATEWAY) {
if (rti_info[RTAX_GATEWAY]->sa_family == AF_LINK) {
const struct sockaddr_dl *sdl;
sdl = (const struct sockaddr_dl*)
(const void *)rti_info[RTAX_GATEWAY];
if (sdl->sdl_alen != 0)
rt->rt_dflags |= RTDF_GATELINK;
} else if (rtm->rtm_flags & RTF_GATEWAY)
COPYSA(&rt->rt_gateway, rti_info[RTAX_GATEWAY]);
}
if (rtm->rtm_addrs & RTA_SRC)
COPYSA(&rt->rt_ifa, rti_info[RTAX_SRC]);
rt->rt_mtu = (unsigned int)rtm->rtm_rmx.rmx_mtu;
if (rtm->rtm_index)
rt->rt_ifp = if_findindex(ctx->ifaces, rtm->rtm_index);
else if (rtm->rtm_addrs & RTA_IFP)
rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_IFP]);
else if (rtm->rtm_addrs & RTA_GATEWAY)
rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_GATEWAY]);
else
rt->rt_ifp = if_findsa(ctx, rti_info[RTAX_DST]);
if (rt->rt_ifp == NULL && rtm->rtm_type == RTM_MISS)
rt->rt_ifp = if_loopback(ctx);
if (rt->rt_ifp == NULL) {
errno = ESRCH;
return -1;
}
return 0;
}
static struct rt *
if_route_get(struct dhcpcd_ctx *ctx, struct rt *rt)
{
struct rtm rtm;
int s;
struct iovec iov = { .iov_base = &rtm, .iov_len = sizeof(rtm) };
struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 };
ssize_t len;
struct rt *rtw = rt;
if_route0(ctx, &rtm, RTM_GET, rt);
rt = NULL;
s = socket(PF_ROUTE, SOCK_RAW | SOCK_CLOEXEC, 0);
if (s == -1)
return NULL;
if (write(s, &rtm, rtm.hdr.rtm_msglen) == -1)
goto out;
if ((len = recvmsg(s, &msg, 0)) == -1)
goto out;
if ((size_t)len < sizeof(rtm.hdr) || len < rtm.hdr.rtm_msglen) {
errno = EINVAL;
goto out;
}
if (if_copyrt(ctx, rtw, &rtm.hdr) == -1)
goto out;
rt = rtw;
out:
close(s);
return rt;
}
static int
if_finishrt(struct dhcpcd_ctx *ctx, struct rt *rt)
{
int mtu;
/* Solaris has a subnet route with the gateway
* of the owning address.
* dhcpcd has a blank gateway here to indicate a
* subnet route. */
if (!sa_is_unspecified(&rt->rt_dest) &&
!sa_is_unspecified(&rt->rt_gateway))
{
switch(rt->rt_gateway.sa_family) {
#ifdef INET
case AF_INET:
{
struct in_addr *in;
in = &satosin(&rt->rt_gateway)->sin_addr;
if (ipv4_findaddr(ctx, in))
in->s_addr = INADDR_ANY;
break;
}
#endif
#ifdef INET6
case AF_INET6:
{
struct in6_addr *in6;
in6 = &satosin6(&rt->rt_gateway)->sin6_addr;
if (ipv6_findaddr(ctx, in6, 0))
*in6 = in6addr_any;
break;
}
#endif
}
}
/* Solaris doesn't set interfaces for some routes.
* This sucks, so we need to call RTM_GET to
* work out the interface. */
if (rt->rt_ifp == NULL) {
if (if_route_get(ctx, rt) == NULL) {
rt->rt_ifp = if_loopback(ctx);
if (rt->rt_ifp == NULL)
return - 1;
}
}
/* Solaris likes to set route MTU to match
* interface MTU when adding routes.
* This confuses dhcpcd as it expects MTU to be 0
* when no explicit MTU has been set. */
mtu = if_getmtu(rt->rt_ifp);
if (mtu == -1)
return -1;
if (rt->rt_mtu == (unsigned int)mtu)
rt->rt_mtu = 0;
return 0;
}
static int
if_addrflags0(int fd, int af, const char *ifname)
{
struct lifreq lifr;
int flags;
memset(&lifr, 0, sizeof(lifr));
strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1)
return -1;
flags = 0;
if (lifr.lifr_flags & IFF_DUPLICATE)
flags |= af == AF_INET6 ? IN6_IFF_DUPLICATED:IN_IFF_DUPLICATED;
else if (!(lifr.lifr_flags & IFF_UP))
flags |= af == AF_INET6 ? IN6_IFF_TENTATIVE:IN_IFF_TENTATIVE;
return flags;
}
static int
if_rtm(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm)
{
const struct sockaddr *sa;
struct rt rt;
if (rtm->rtm_msglen < sizeof(*rtm) + sizeof(*sa)) {
errno = EINVAL;
return -1;
}
if (if_copyrt(ctx, &rt, rtm) == -1 && errno != ESRCH)
return -1;
#ifdef INET6
/*
* BSD announces host routes.
* As such, we should be notified of reachability by its
* existance with a hardware address.
* Ensure we don't call this for a newly incomplete state.
*/
if (rt.rt_dest.sa_family == AF_INET6 &&
(rt.rt_flags & RTF_HOST || rtm->rtm_type == RTM_MISS) &&
!(rtm->rtm_type == RTM_ADD && !(rt.rt_dflags & RTDF_GATELINK)))
{
bool reachable;
reachable = (rtm->rtm_type == RTM_ADD ||
rtm->rtm_type == RTM_CHANGE) &&
rt.rt_dflags & RTDF_GATELINK;
ipv6nd_neighbour(ctx, &rt.rt_ss_dest.sin6.sin6_addr, reachable);
}
#endif
if (if_finishrt(ctx, &rt) == -1)
return -1;
rt_recvrt(rtm->rtm_type, &rt, rtm->rtm_pid);
return 0;
}
static bool
if_getalias(struct interface *ifp, const struct sockaddr *sa, char *alias)
{
struct ifaddrs *ifaddrs, *ifa;
struct interface *ifpx;
bool found;
ifaddrs = NULL;
if (getallifaddrs(sa->sa_family, &ifaddrs, 0) == -1)
return false;
found = false;
for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL)
continue;
if (sa_cmp(sa, ifa->ifa_addr) != 0)
continue;
/* Check it's for the right interace. */
ifpx = if_find(ifp->ctx->ifaces, ifa->ifa_name);
if (ifp == ifpx) {
strlcpy(alias, ifa->ifa_name, IF_NAMESIZE);
found = true;
break;
}
}
freeifaddrs(ifaddrs);
return found;
}
static int
if_getbrdaddr(struct dhcpcd_ctx *ctx, const char *ifname, struct in_addr *brd)
{
struct lifreq lifr = { 0 };
int r;
memset(&lifr, 0, sizeof(lifr));
strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
errno = 0;
r = ioctl(ctx->pf_inet_fd, SIOCGLIFBRDADDR, &lifr, sizeof(lifr));
if (r != -1)
COPYOUT(*brd, (struct sockaddr *)&lifr.lifr_broadaddr);
return r;
}
static int
if_ifa(struct dhcpcd_ctx *ctx, const struct ifa_msghdr *ifam)
{
struct interface *ifp;
const struct sockaddr *sa, *rti_info[RTAX_MAX];
int flags;
char ifalias[IF_NAMESIZE];
if (ifam->ifam_msglen < sizeof(*ifam)) {
errno = EINVAL;
return -1;
}
if (~ifam->ifam_addrs & RTA_IFA)
return 0;
if (get_addrs(ifam->ifam_addrs, (const char *)ifam + sizeof(*ifam),
ifam->ifam_msglen - sizeof(*ifam), rti_info) == -1)
return -1;
sa = rti_info[RTAX_IFA];
/* XXX We have no way of knowing who generated these
* messages wich truely sucks because we want to
* avoid listening to our own delete messages. */
if ((ifp = if_findindex(ctx->ifaces, ifam->ifam_index)) == NULL)
return 0;
/*
* ifa_msghdr does not supply the alias, just the interface index.
* This is very bad, because it means we have to call getifaddrs
* and trawl the list of addresses to find the added address.
* To make life worse, you can have the same address on the same
* interface with different aliases.
* So this hack is not entirely accurate.
*
* IF anyone is going to fix Solaris, plesse consider adding the
* following fields to extend ifa_msghdr:
* ifam_alias
* ifam_pid
*/
if (ifam->ifam_type != RTM_DELADDR && !if_getalias(ifp, sa, ifalias))
return 0;
switch (sa->sa_family) {
case AF_LINK:
{
struct sockaddr_dl sdl;
if (ifam->ifam_type != RTM_CHGADDR &&
ifam->ifam_type != RTM_NEWADDR)
break;
memcpy(&sdl, rti_info[RTAX_IFA], sizeof(sdl));
dhcpcd_handlehwaddr(ifp, ifp->hwtype,
CLLADDR(&sdl), sdl.sdl_alen);
break;
}
#ifdef INET
case AF_INET:
{
struct in_addr addr, mask, bcast;
COPYOUT(addr, rti_info[RTAX_IFA]);
COPYOUT(mask, rti_info[RTAX_NETMASK]);
COPYOUT(bcast, rti_info[RTAX_BRD]);
if (ifam->ifam_type == RTM_DELADDR) {
struct ipv4_addr *ia;
ia = ipv4_iffindaddr(ifp, &addr, &mask);
if (ia == NULL)
return 0;
strlcpy(ifalias, ia->alias, sizeof(ifalias));
} else if (bcast.s_addr == INADDR_ANY) {
/* Work around a bug where broadcast
* address is not correctly reported. */
if (if_getbrdaddr(ctx, ifalias, &bcast) == -1)
return 0;
}
flags = if_addrflags(ifp, NULL, ifalias);
if (ifam->ifam_type == RTM_DELADDR) {
if (flags != -1)
return 0;
} else if (flags == -1)
return 0;
ipv4_handleifa(ctx,
ifam->ifam_type == RTM_CHGADDR ?
RTM_NEWADDR : ifam->ifam_type,
NULL, ifalias, &addr, &mask, &bcast, flags, 0);
break;
}
#endif
#ifdef INET6
case AF_INET6:
{
struct in6_addr addr6, mask6;
const struct sockaddr_in6 *sin6;
sin6 = (const void *)rti_info[RTAX_IFA];
addr6 = sin6->sin6_addr;
sin6 = (const void *)rti_info[RTAX_NETMASK];
mask6 = sin6->sin6_addr;
if (ifam->ifam_type == RTM_DELADDR) {
struct ipv6_addr *ia;
ia = ipv6_iffindaddr(ifp, &addr6, 0);
if (ia == NULL)
return 0;
strlcpy(ifalias, ia->alias, sizeof(ifalias));
}
flags = if_addrflags6(ifp, NULL, ifalias);
if (ifam->ifam_type == RTM_DELADDR) {
if (flags != -1)
return 0;
} else if (flags == -1)
return 0;
ipv6_handleifa(ctx,
ifam->ifam_type == RTM_CHGADDR ?
RTM_NEWADDR : ifam->ifam_type,
NULL, ifalias, &addr6, ipv6_prefixlen(&mask6), flags, 0);
break;
}
#endif
}
return 0;
}
static int
if_ifinfo(struct dhcpcd_ctx *ctx, const struct if_msghdr *ifm)
{
struct interface *ifp;
int state;
unsigned int flags;
if (ifm->ifm_msglen < sizeof(*ifm)) {
errno = EINVAL;
return -1;
}
if ((ifp = if_findindex(ctx->ifaces, ifm->ifm_index)) == NULL)
return 0;
flags = (unsigned int)ifm->ifm_flags;
if (ifm->ifm_flags & IFF_OFFLINE)
state = LINK_DOWN;
else {
state = LINK_UP;
flags |= IFF_UP;
}
dhcpcd_handlecarrier(ifp, state, flags);
return 0;
}
static int
if_dispatch(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm)
{
if (rtm->rtm_version != RTM_VERSION)
return 0;
switch(rtm->rtm_type) {
case RTM_IFINFO:
return if_ifinfo(ctx, (const void *)rtm);
case RTM_ADD: /* FALLTHROUGH */
case RTM_CHANGE: /* FALLTHROUGH */
case RTM_DELETE: /* FALLTHROUGH */
case RTM_MISS:
return if_rtm(ctx, (const void *)rtm);
case RTM_CHGADDR: /* FALLTHROUGH */
case RTM_DELADDR: /* FALLTHROUGH */
case RTM_NEWADDR:
return if_ifa(ctx, (const void *)rtm);
}
return 0;
}
int
if_handlelink(struct dhcpcd_ctx *ctx)
{
struct rtm rtm;
ssize_t len;
len = read(ctx->link_fd, &rtm, sizeof(rtm));
if (len == -1)
return -1;
if (len == 0)
return 0;
if ((size_t)len < sizeof(rtm.hdr.rtm_msglen) ||
len != rtm.hdr.rtm_msglen)
{
errno = EINVAL;
return -1;
}
/*
* Coverity thinks that the data could be tainted from here.
* I have no idea how because the length of the data we read
* is guarded by len and checked to match rtm_msglen.
* The issue seems to be related to extracting the addresses
* at the end of the header, but seems to have no issues with the
* equivalent call in if_initrt.
*/
/* coverity[tainted_data] */
return if_dispatch(ctx, &rtm.hdr);
}
static void
if_octetstr(char *buf, const Octet_t *o, ssize_t len)
{
int i;
char *p;
p = buf;
for (i = 0; i < o->o_length; i++) {
if ((p + 1) - buf < len)
*p++ = o->o_bytes[i];
else
break;
}
*p = '\0';
}
static int
if_setflags(int fd, const char *ifname, uint64_t flags)
{
struct lifreq lifr = { .lifr_addrlen = 0 };
strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1)
return -1;
if ((lifr.lifr_flags & flags) != flags) {
lifr.lifr_flags |= flags;
if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1)
return -1;
}
return 0;
}
static int
if_addaddr(int fd, const char *ifname,
struct sockaddr_storage *addr, struct sockaddr_storage *mask,
struct sockaddr_storage *brd, uint8_t plen)
{
struct lifreq lifr = { .lifr_addrlen = plen };
strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
/* First assign the netmask. */
lifr.lifr_addr = *mask;
if (addr == NULL) {
lifr.lifr_addrlen = plen;
if (ioctl(fd, SIOCSLIFSUBNET, &lifr) == -1)
return -1;
goto up;
} else {
if (ioctl(fd, SIOCSLIFNETMASK, &lifr) == -1)
return -1;
}
/* Then assign the address. */
lifr.lifr_addr = *addr;
if (ioctl(fd, SIOCSLIFADDR, &lifr) == -1)
return -1;
/* Then assign the broadcast address. */
if (brd != NULL) {
lifr.lifr_broadaddr = *brd;
if (ioctl(fd, SIOCSLIFBRDADDR, &lifr) == -1)
return -1;
}
up:
return if_setflags(fd, ifname, IFF_UP);
}
static int
if_getaf_fd(const struct dhcpcd_ctx *ctx, int af)
{
if (af == AF_INET)
return ctx->pf_inet_fd;
if (af == AF_INET6) {
struct priv *priv;
priv = (struct priv *)ctx->priv;
return priv->pf_inet6_fd;
}
errno = EAFNOSUPPORT;
return -1;
}
int
if_getsubnet(struct dhcpcd_ctx *ctx, const char *ifname, int af,
void *subnet, size_t subnet_len)
{
struct lifreq lifr = { .lifr_addrlen = 0 };
int fd;
fd = if_getaf_fd(ctx, af);
strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
if (ioctl(fd, SIOCGLIFSUBNET, &lifr) == -1)
return -1;
memcpy(subnet, &lifr.lifr_addr, MIN(subnet_len,sizeof(lifr.lifr_addr)));
return 0;
}
static int
if_plumblif(int cmd, const struct dhcpcd_ctx *ctx, int af, const char *ifname)
{
struct lifreq lifr;
int s;
memset(&lifr, 0, sizeof(lifr));
strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
lifr.lifr_addr.ss_family = af;
s = if_getaf_fd(ctx, af);
return ioctl(s,
cmd == RTM_NEWADDR ? SIOCLIFADDIF : SIOCLIFREMOVEIF,
&lifr) == -1 && errno != EEXIST ? -1 : 0;
}
static int
if_plumbif(const struct dhcpcd_ctx *ctx, int af, const char *ifname)
{
dlpi_handle_t dh, dh_arp = NULL;
int fd, af_fd, mux_fd, arp_fd = -1, mux_id, retval;
uint64_t flags;
struct lifreq lifr;
const char *udp_dev;
struct strioctl ioc;
struct if_spec spec;
if (if_nametospec(ifname, &spec) == -1)
return -1;
af_fd = if_getaf_fd(ctx, af);
switch (af) {
case AF_INET:
flags = IFF_IPV4;
udp_dev = UDP_DEV_NAME;
break;
case AF_INET6:
/* We will take care of setting the link local address. */
flags = IFF_IPV6 | IFF_NOLINKLOCAL;
udp_dev = UDP6_DEV_NAME;
break;
default:
errno = EPROTONOSUPPORT;
return -1;
}
if (dlpi_open(ifname, &dh, DLPI_NOATTACH) != DLPI_SUCCESS) {
errno = EINVAL;
return -1;
}
fd = dlpi_fd(dh);
retval = -1;
mux_fd = -1;
if (ioctl(fd, I_PUSH, IP_MOD_NAME) == -1)
goto out;
memset(&lifr, 0, sizeof(lifr));
strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
lifr.lifr_ppa = spec.ppa;
lifr.lifr_flags = flags;
if (ioctl(fd, SIOCSLIFNAME, &lifr) == -1)
goto out;
/* Get full flags. */
if (ioctl(af_fd, SIOCGLIFFLAGS, &lifr) == -1)
goto out;
flags = lifr.lifr_flags;
/* Open UDP as a multiplexor to PLINK the interface stream.
* UDP is used because STREAMS will not let you PLINK a driver
* under itself and IP is generally at the bottom of the stream. */
if ((mux_fd = open(udp_dev, O_RDWR)) == -1)
goto out;
/* POP off all undesired modules. */
while (ioctl(mux_fd, I_POP, 0) != -1)
;
if (errno != EINVAL)
goto out;
if(ioctl(mux_fd, I_PUSH, ARP_MOD_NAME) == -1)
goto out;
if (flags & (IFF_NOARP | IFF_IPV6)) {
/* PLINK the interface stream so it persists. */
if (ioctl(mux_fd, I_PLINK, fd) == -1)
goto out;
goto done;
}
if (dlpi_open(ifname, &dh_arp, DLPI_NOATTACH) != DLPI_SUCCESS)
goto out;
arp_fd = dlpi_fd(dh_arp);
if (ioctl(arp_fd, I_PUSH, ARP_MOD_NAME) == -1)
goto out;
memset(&lifr, 0, sizeof(lifr));
strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
lifr.lifr_ppa = spec.ppa;
lifr.lifr_flags = flags;
memset(&ioc, 0, sizeof(ioc));
ioc.ic_cmd = SIOCSLIFNAME;
ioc.ic_dp = (char *)&lifr;
ioc.ic_len = sizeof(lifr);
if (ioctl(arp_fd, I_STR, &ioc) == -1)
goto out;
/* PLINK the interface stream so it persists. */
mux_id = ioctl(mux_fd, I_PLINK, fd);
if (mux_id == -1)
goto out;
if (ioctl(mux_fd, I_PLINK, arp_fd) == -1) {
ioctl(mux_fd, I_PUNLINK, mux_id);
goto out;
}
done:
retval = 0;
out:
dlpi_close(dh);
if (dh_arp != NULL)
dlpi_close(dh_arp);
if (mux_fd != -1)
close(mux_fd);
return retval;
}
static int
if_unplumbif(const struct dhcpcd_ctx *ctx, int af, const char *ifname)
{
struct sockaddr_storage addr = { .ss_family = af };
int fd;
/* For the time being, don't unplumb the interface, just
* set the address to zero. */
fd = if_getaf_fd(ctx, af);
return if_addaddr(fd, ifname, &addr, &addr,
af == AF_INET ? &addr : NULL, 0);
}
static int
if_plumb(int cmd, const struct dhcpcd_ctx *ctx, int af, const char *ifname)
{
struct if_spec spec;
if (if_nametospec(ifname, &spec) == -1)
return -1;
if (spec.lun != -1)
return if_plumblif(cmd, ctx, af, ifname);
if (cmd == RTM_NEWADDR)
return if_plumbif(ctx, af, ifname);
else
return if_unplumbif(ctx, af, ifname);
}
#ifdef INET
static int
if_walkrt(struct dhcpcd_ctx *ctx, rb_tree_t *routes, char *data, size_t len)
{
mib2_ipRouteEntry_t *re, *e;
struct rt rt, *rtn;
char ifname[IF_NAMESIZE];
struct in_addr in;
if (len % sizeof(*re) != 0) {
errno = EINVAL;
return -1;
}
re = (mib2_ipRouteEntry_t *)data;
e = (mib2_ipRouteEntry_t *)(data + len);
do {
/* Skip route types we don't want. */
switch (re->ipRouteInfo.re_ire_type) {
case IRE_IF_CLONE:
case IRE_BROADCAST:
case IRE_MULTICAST:
case IRE_NOROUTE:
case IRE_LOCAL:
continue;
default:
break;
}
memset(&rt, 0, sizeof(rt));
in.s_addr = re->ipRouteDest;
sa_in_init(&rt.rt_dest, &in);
in.s_addr = re->ipRouteMask;
sa_in_init(&rt.rt_netmask, &in);
in.s_addr = re->ipRouteNextHop;
sa_in_init(&rt.rt_gateway, &in);
rt.rt_flags = re->ipRouteInfo.re_flags;
in.s_addr = re->ipRouteInfo.re_src_addr;
sa_in_init(&rt.rt_ifa, &in);
rt.rt_mtu = re->ipRouteInfo.re_max_frag;
if_octetstr(ifname, &re->ipRouteIfIndex, sizeof(ifname));
rt.rt_ifp = if_find(ctx->ifaces, ifname);
if (if_finishrt(ctx, &rt) == -1) {
logerr(__func__);
continue;
}
if ((rtn = rt_new(rt.rt_ifp)) == NULL) {
logerr(__func__);
break;
}
memcpy(rtn, &rt, sizeof(*rtn));
if (rb_tree_insert_node(routes, rtn) != rtn)
rt_free(rtn);
} while (++re < e);
return 0;
}
#endif
#ifdef INET6
static int
if_walkrt6(struct dhcpcd_ctx *ctx, rb_tree_t *routes, char *data, size_t len)
{
mib2_ipv6RouteEntry_t *re, *e;
struct rt rt, *rtn;
char ifname[IF_NAMESIZE];
struct in6_addr in6;
if (len % sizeof(*re) != 0) {
errno = EINVAL;
return -1;
}
re = (mib2_ipv6RouteEntry_t *)data;
e = (mib2_ipv6RouteEntry_t *)(data + len);
do {
/* Skip route types we don't want. */
switch (re->ipv6RouteInfo.re_ire_type) {
case IRE_IF_CLONE:
case IRE_BROADCAST:
case IRE_MULTICAST:
case IRE_NOROUTE:
case IRE_LOCAL:
continue;
default:
break;
}
memset(&rt, 0, sizeof(rt));
sa_in6_init(&rt.rt_dest, &re->ipv6RouteDest);
ipv6_mask(&in6, re->ipv6RoutePfxLength);
sa_in6_init(&rt.rt_netmask, &in6);
sa_in6_init(&rt.rt_gateway, &re->ipv6RouteNextHop);
sa_in6_init(&rt.rt_ifa, &re->ipv6RouteInfo.re_src_addr);
rt.rt_mtu = re->ipv6RouteInfo.re_max_frag;
if_octetstr(ifname, &re->ipv6RouteIfIndex, sizeof(ifname));
rt.rt_ifp = if_find(ctx->ifaces, ifname);
if (if_finishrt(ctx, &rt) == -1) {
logerr(__func__);
continue;
}
if ((rtn = rt_new(rt.rt_ifp)) == NULL) {
logerr(__func__);
break;
}
memcpy(rtn, &rt, sizeof(*rtn));
if (rb_tree_insert_node(routes, rtn) != rtn)
rt_free(rtn);
} while (++re < e);
return 0;
}
#endif
static int
if_parsert(struct dhcpcd_ctx *ctx, rb_tree_t *routes,
unsigned int level, unsigned int name,
int (*walkrt)(struct dhcpcd_ctx *, rb_tree_t *, char *, size_t))
{
int s, retval, code, flags;
uintptr_t buf[512 / sizeof(uintptr_t)];
struct strbuf ctlbuf, databuf;
struct T_optmgmt_req *tor = (struct T_optmgmt_req *)buf;
struct T_optmgmt_ack *toa = (struct T_optmgmt_ack *)buf;
struct T_error_ack *tea = (struct T_error_ack *)buf;
struct opthdr *req;
if ((s = open("/dev/arp", O_RDWR)) == -1)
return -1;
/* Assume we are erroring. */
retval = -1;
tor->PRIM_type = T_SVR4_OPTMGMT_REQ;
tor->OPT_offset = sizeof (struct T_optmgmt_req);
tor->OPT_length = sizeof (struct opthdr);
tor->MGMT_flags = T_CURRENT;
req = (struct opthdr *)&tor[1];
req->level = EXPER_IP_AND_ALL_IRES;
req->name = 0;
req->len = 1;
ctlbuf.buf = (char *)buf;
ctlbuf.len = tor->OPT_length + tor->OPT_offset;
if (putmsg(s, &ctlbuf, NULL, 0) == 1)
goto out;
req = (struct opthdr *)&toa[1];
ctlbuf.maxlen = sizeof(buf);
/* Create a reasonable buffer to start with */
databuf.maxlen = BUFSIZ * 2;
if ((databuf.buf = malloc(databuf.maxlen)) == NULL)
goto out;
for (;;) {
flags = 0;
if ((code = getmsg(s, &ctlbuf, 0, &flags)) == -1)
break;
if (code == 0 &&
toa->PRIM_type == T_OPTMGMT_ACK &&
toa->MGMT_flags == T_SUCCESS &&
(size_t)ctlbuf.len >= sizeof(struct T_optmgmt_ack))
{
/* End of messages, so return success! */
retval = 0;
break;
}
if (tea->PRIM_type == T_ERROR_ACK) {
errno = tea->TLI_error == TSYSERR ?
tea->UNIX_error : EPROTO;
break;
}
if (code != MOREDATA ||
toa->PRIM_type != T_OPTMGMT_ACK ||
toa->MGMT_flags != T_SUCCESS)
{
errno = ENOMSG;
break;
}
/* Try to ensure out buffer is big enough
* for future messages as well. */
if ((size_t)databuf.maxlen < req->len) {
size_t newlen;
free(databuf.buf);
newlen = roundup(req->len, BUFSIZ);
if ((databuf.buf = malloc(newlen)) == NULL)
break;
databuf.maxlen = newlen;
}
flags = 0;
if (getmsg(s, NULL, &databuf, &flags) == -1)
break;
/* We always have to get the data before moving onto
* the next item, so don't move this test higher up
* to avoid the buffer allocation and getmsg calls. */
if (req->level == level && req->name == name) {
if (walkrt(ctx, routes, databuf.buf, req->len) == -1)
break;
}
}
free(databuf.buf);
out:
close(s);
return retval;
}
int
if_initrt(struct dhcpcd_ctx *ctx, rb_tree_t *routes, int af)
{
#ifdef INET
if ((af == AF_UNSPEC || af == AF_INET) &&
if_parsert(ctx, routes, MIB2_IP,MIB2_IP_ROUTE, if_walkrt) == -1)
return -1;
#endif
#ifdef INET6
if ((af == AF_UNSPEC || af == AF_INET6) &&
if_parsert(ctx, routes, MIB2_IP6, MIB2_IP6_ROUTE, if_walkrt6) == -1)
return -1;
#endif
return 0;
}
#ifdef INET
/* XXX We should fix this to write via the BPF interface. */
ssize_t
bpf_send(const struct bpf *bpf, uint16_t protocol, const void *data, size_t len)
{
const struct interface *ifp = bpf->bpf_ifp;
dlpi_handle_t dh;
dlpi_info_t di;
int r;
if (dlpi_open(ifp->name, &dh, 0) != DLPI_SUCCESS)
return -1;
if ((r = dlpi_info(dh, &di, 0)) == DLPI_SUCCESS &&
(r = dlpi_bind(dh, protocol, NULL)) == DLPI_SUCCESS)
r = dlpi_send(dh, di.di_bcastaddr, ifp->hwlen, data, len, NULL);
dlpi_close(dh);
return r == DLPI_SUCCESS ? (ssize_t)len : -1;
}
int
if_address(unsigned char cmd, const struct ipv4_addr *ia)
{
union {
struct sockaddr sa;
struct sockaddr_storage ss;
} addr, mask, brd;
int fd = ia->iface->ctx->pf_inet_fd;
/* Either remove the alias or ensure it exists. */
if (if_plumb(cmd, ia->iface->ctx, AF_INET, ia->alias) == -1 &&
errno != EEXIST)
return -1;
if (cmd == RTM_DELADDR)
return 0;
if (cmd != RTM_NEWADDR) {
errno = EINVAL;
return -1;
}
/* We need to update the index now */
ia->iface->index = if_nametoindex(ia->alias);
sa_in_init(&addr.sa, &ia->addr);
sa_in_init(&mask.sa, &ia->mask);
sa_in_init(&brd.sa, &ia->brd);
return if_addaddr(fd, ia->alias, &addr.ss, &mask.ss, &brd.ss, 0);
}
int
if_addrflags(const struct interface *ifp, __unused const struct in_addr * ia,
const char *alias)
{
return if_addrflags0(ifp->ctx->pf_inet_fd, AF_INET, alias);
}
#endif
#ifdef INET6
int
if_address6(unsigned char cmd, const struct ipv6_addr *ia)
{
union {
struct sockaddr sa;
struct sockaddr_in6 sin6;
struct sockaddr_storage ss;
} addr, mask;
int fd, r;
/* Either remove the alias or ensure it exists. */
if (if_plumb(cmd, ia->iface->ctx, AF_INET6, ia->alias) == -1 &&
errno != EEXIST)
return -1;
if (cmd == RTM_DELADDR)
return 0;
if (cmd != RTM_NEWADDR) {
errno = EINVAL;
return -1;
}
fd = if_getaf_fd(ia->iface->ctx, AF_INET6);
if (!(ia->flags & IPV6_AF_AUTOCONF) && ia->flags & IPV6_AF_RAPFX) {
if (if_setflags(fd, ia->alias, IFF_NOLOCAL) ==-1)
return -1;
sa_in6_init(&mask.sa, &ia->prefix);
r = if_addaddr(fd, ia->alias,
NULL, &mask.ss, NULL, ia->prefix_len);
} else {
sa_in6_init(&addr.sa, &ia->addr);
mask.sin6.sin6_family = AF_INET6;
ipv6_mask(&mask.sin6.sin6_addr, ia->prefix_len);
r = if_addaddr(fd, ia->alias,
&addr.ss, &mask.ss, NULL, ia->prefix_len);
}
if (r == -1 && errno == EEXIST)
return 0;
return r;
}
int
if_addrflags6(const struct interface *ifp, __unused const struct in6_addr *ia,
const char *alias)
{
int fd;
fd = if_getaf_fd(ifp->ctx, AF_INET6);
return if_addrflags0(fd, AF_INET6, alias);
}
int
if_getlifetime6(struct ipv6_addr *addr)
{
UNUSED(addr);
errno = ENOTSUP;
return -1;
}
int
if_applyra(const struct ra *rap)
{
struct lifreq lifr = {
.lifr_ifinfo.lir_maxhops = rap->hoplimit,
.lifr_ifinfo.lir_reachtime = rap->reachable,
.lifr_ifinfo.lir_reachretrans = rap->retrans,
};
strlcpy(lifr.lifr_name, rap->iface->name, sizeof(lifr.lifr_name));
return ioctl(rap->iface->ctx->pf_inet_fd, SIOCSLIFLNKINFO, &lifr);
}
void
if_setup_inet6(__unused const struct interface *ifp)
{
}
int
ip6_forwarding(__unused const char *ifname)
{
return 1;
}
#endif