/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
*/
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <net/if.h>
#include <net/route.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/fcntl.h>
#include <unistd.h>
#include <libnwam.h>
#include "events.h"
#include "ncp.h"
#include "ncu.h"
#include "util.h"
/*
* routing_events.c - this file contains routines to retrieve routing socket
* events and package them for high level processing.
*/
#define RTMBUFSZ sizeof (struct rt_msghdr) + \
(RTAX_MAX * sizeof (struct sockaddr_storage))
static void printaddrs(int, void *);
static char *printaddr(void **);
static void *getaddr(int, int, void *);
static void setaddr(int, int *, void *, struct sockaddr *);
union rtm_buf
{
/* Routing information. */
struct
{
struct rt_msghdr rtm;
struct sockaddr_storage addr[RTAX_MAX];
} r;
/* Interface information. */
struct
{
struct if_msghdr ifm;
struct sockaddr_storage addr[RTAX_MAX];
} im;
/* Interface address information. */
struct
{
struct ifa_msghdr ifa;
struct sockaddr_storage addr[RTAX_MAX];
} ia;
};
static int v4_sock = -1;
static int v6_sock = -1;
static pthread_t v4_routing, v6_routing;
static int seq = 0;
static const char *
rtmtype_str(int type)
{
static char typestr[12]; /* strlen("type ") + enough for an int */
switch (type) {
case RTM_NEWADDR:
return ("NEWADDR");
case RTM_DELADDR:
return ("DELADDR");
case RTM_CHGADDR:
return ("CHGADDR");
case RTM_FREEADDR:
return ("FREEADDR");
default:
(void) snprintf(typestr, sizeof (typestr), "type %d", type);
return (typestr);
}
}
/* ARGSUSED0 */
static void *
routing_events_v4(void *arg)
{
int n;
union rtm_buf buffer;
struct rt_msghdr *rtm;
struct ifa_msghdr *ifa;
char *addrs, *if_name;
struct sockaddr_dl *addr_dl;
struct sockaddr *addr, *netmask;
nwamd_event_t ip_event;
nlog(LOG_DEBUG, "v4 routing socket %d", v4_sock);
for (;;) {
rtm = &buffer.r.rtm;
n = read(v4_sock, &buffer, sizeof (buffer));
if (n == -1 && errno == EAGAIN) {
continue;
} else if (n == -1) {
nlog(LOG_ERR, "error reading routing socket "
"%d: %m", v4_sock);
/* Low likelihood. What's recovery path? */
continue;
}
if (rtm->rtm_msglen < n) {
nlog(LOG_ERR, "only read %d bytes from "
"routing socket but message claims to be "
"of length %d", rtm->rtm_msglen);
continue;
}
if (rtm->rtm_version != RTM_VERSION) {
nlog(LOG_ERR, "tossing routing message of "
"version %d type %d", rtm->rtm_version,
rtm->rtm_type);
continue;
}
if (rtm->rtm_msglen != n) {
nlog(LOG_DEBUG, "routing message of %d size came from "
"read of %d on socket %d", rtm->rtm_msglen,
n, v4_sock);
}
switch (rtm->rtm_type) {
case RTM_NEWADDR:
case RTM_DELADDR:
case RTM_CHGADDR:
case RTM_FREEADDR:
ifa = (void *)rtm;
addrs = (char *)ifa + sizeof (*ifa);
nlog(LOG_DEBUG, "v4 routing message %s: "
"index %d flags %x", rtmtype_str(rtm->rtm_type),
ifa->ifam_index, ifa->ifam_flags);
if ((addr = (struct sockaddr *)getaddr(RTA_IFA,
ifa->ifam_addrs, addrs)) == NULL)
break;
/* Ignore routing socket messages for 0.0.0.0 */
/*LINTED*/
if (((struct sockaddr_in *)addr)->sin_addr.s_addr
== INADDR_ANY) {
nlog(LOG_DEBUG, "routing_events_v4: "
"tossing message for 0.0.0.0");
break;
}
if ((netmask = (struct sockaddr *)getaddr(RTA_NETMASK,
ifa->ifam_addrs, addrs)) == NULL)
break;
if ((addr_dl = (struct sockaddr_dl *)getaddr
(RTA_IFP, ifa->ifam_addrs, addrs)) == NULL)
break;
/*
* We don't use the lladdr in this structure so we can
* run over it.
*/
addr_dl->sdl_data[addr_dl->sdl_nlen] = 0;
if_name = addr_dl->sdl_data; /* no lifnum */
if (ifa->ifam_index == 0) {
nlog(LOG_DEBUG, "tossing index 0 message");
break;
}
if (ifa->ifam_type != rtm->rtm_type) {
nlog(LOG_INFO,
"routing_events_v4: unhandled type %d",
ifa->ifam_type);
break;
}
printaddrs(ifa->ifam_addrs, addrs);
/* Create and enqueue IF_STATE event */
ip_event = nwamd_event_init_if_state(if_name,
ifa->ifam_flags,
(rtm->rtm_type == RTM_NEWADDR ||
rtm->rtm_type == RTM_CHGADDR ? B_TRUE : B_FALSE),
addr, netmask);
if (ip_event != NULL)
nwamd_event_enqueue(ip_event);
break;
}
}
/* NOTREACHED */
return (NULL);
}
/* ARGSUSED0 */
static void *
routing_events_v6(void *arg)
{
int n;
union rtm_buf buffer;
struct rt_msghdr *rtm;
struct ifa_msghdr *ifa;
char *addrs, *if_name;
struct sockaddr_dl *addr_dl;
struct sockaddr *addr, *netmask;
nwamd_event_t ip_event;
nlog(LOG_DEBUG, "v6 routing socket %d", v6_sock);
for (;;) {
rtm = &buffer.r.rtm;
n = read(v6_sock, &buffer, sizeof (buffer));
if (n == -1 && errno == EAGAIN) {
continue;
} else if (n == -1) {
nlog(LOG_ERR, "error reading routing socket "
"%d: %m", v6_sock);
/* Low likelihood. What's recovery path? */
continue;
}
if (rtm->rtm_msglen < n) {
nlog(LOG_ERR, "only read %d bytes from "
"routing socket but message claims to be "
"of length %d", rtm->rtm_msglen);
continue;
}
if (rtm->rtm_version != RTM_VERSION) {
nlog(LOG_ERR, "tossing routing message of "
"version %d type %d", rtm->rtm_version,
rtm->rtm_type);
continue;
}
if (rtm->rtm_msglen != n) {
nlog(LOG_DEBUG, "routing message of %d size came from "
"read of %d on socket %d", rtm->rtm_msglen,
n, v6_sock);
}
switch (rtm->rtm_type) {
case RTM_NEWADDR:
case RTM_DELADDR:
case RTM_CHGADDR:
case RTM_FREEADDR:
ifa = (void *)rtm;
addrs = (char *)ifa + sizeof (*ifa);
nlog(LOG_DEBUG, "v6 routing message %s: "
"index %d flags %x", rtmtype_str(rtm->rtm_type),
ifa->ifam_index, ifa->ifam_flags);
if ((addr = (struct sockaddr *)getaddr(RTA_IFA,
ifa->ifam_addrs, addrs)) == NULL)
break;
/* Ignore routing socket messages for :: & linklocal */
/*LINTED*/
if (IN6_IS_ADDR_UNSPECIFIED(
&((struct sockaddr_in6 *)addr)->sin6_addr)) {
nlog(LOG_INFO, "routing_events_v6: "
"tossing message for ::");
break;
}
/*LINTED*/
if (IN6_IS_ADDR_LINKLOCAL(
&((struct sockaddr_in6 *)addr)->sin6_addr)) {
nlog(LOG_INFO, "routing_events_v6: "
"tossing message for link local address");
break;
}
if ((netmask =
(struct sockaddr *)getaddr(RTA_NETMASK,
ifa->ifam_addrs, addrs)) == NULL)
break;
if ((addr_dl = (struct sockaddr_dl *)getaddr
(RTA_IFP, ifa->ifam_addrs, addrs)) == NULL)
break;
/*
* We don't use the lladdr in this structure so we can
* run over it.
*/
addr_dl->sdl_data[addr_dl->sdl_nlen] = 0;
if_name = addr_dl->sdl_data; /* no lifnum */
if (ifa->ifam_index == 0) {
nlog(LOG_DEBUG, "tossing index 0 message");
break;
}
if (ifa->ifam_type != rtm->rtm_type) {
nlog(LOG_DEBUG,
"routing_events_v6: unhandled type %d",
ifa->ifam_type);
break;
}
printaddrs(ifa->ifam_addrs, addrs);
/* Create and enqueue IF_STATE event */
ip_event = nwamd_event_init_if_state(if_name,
ifa->ifam_flags,
(rtm->rtm_type == RTM_NEWADDR ||
rtm->rtm_type == RTM_CHGADDR ? B_TRUE : B_FALSE),
addr, netmask);
if (ip_event != NULL)
nwamd_event_enqueue(ip_event);
break;
}
}
/* NOTREACHED */
return (NULL);
}
void
nwamd_routing_events_init(void)
{
pthread_attr_t attr;
/*
* Initialize routing sockets here so that we know the routing threads
* (and any requests to add a route) will be working with a valid socket
* by the time we start handling events.
*/
v4_sock = socket(AF_ROUTE, SOCK_RAW, AF_INET);
if (v4_sock == -1)
pfail("failed to open v4 routing socket: %m");
v6_sock = socket(AF_ROUTE, SOCK_RAW, AF_INET6);
if (v6_sock == -1)
pfail("failed to open v6 routing socket: %m");
(void) pthread_attr_init(&attr);
(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (pthread_create(&v4_routing, &attr, routing_events_v4, NULL) != 0 ||
pthread_create(&v6_routing, &attr, routing_events_v6, NULL) != 0)
pfail("routing thread creation failed");
(void) pthread_attr_destroy(&attr);
}
void
nwamd_routing_events_fini(void)
{
(void) pthread_cancel(v4_routing);
(void) pthread_cancel(v6_routing);
}
void
nwamd_add_route(struct sockaddr *dest, struct sockaddr *mask,
struct sockaddr *gateway, const char *ifname)
{
char rtbuf[RTMBUFSZ];
/* LINTED E_BAD_PTR_CAST_ALIGN */
struct rt_msghdr *rtm = (struct rt_msghdr *)rtbuf;
void *addrs = rtbuf + sizeof (struct rt_msghdr);
struct sockaddr_dl sdl;
int rlen, index;
int af;
af = gateway->sa_family;
/* retrieve the index value for the interface */
if ((index = if_nametoindex(ifname)) == 0) {
nlog(LOG_ERR, "nwamd_add_route: if_nametoindex failed on %s",
ifname);
return;
}
(void) bzero(&sdl, sizeof (struct sockaddr_dl));
sdl.sdl_family = AF_LINK;
sdl.sdl_index = index;
(void) bzero(rtm, RTMBUFSZ);
rtm->rtm_pid = getpid();
rtm->rtm_type = RTM_ADD;
rtm->rtm_flags = RTF_UP | RTF_STATIC | RTF_GATEWAY;
rtm->rtm_version = RTM_VERSION;
rtm->rtm_seq = ++seq;
rtm->rtm_msglen = sizeof (rtbuf);
setaddr(RTA_DST, &rtm->rtm_addrs, &addrs, dest);
setaddr(RTA_GATEWAY, &rtm->rtm_addrs, &addrs, gateway);
setaddr(RTA_NETMASK, &rtm->rtm_addrs, &addrs, mask);
setaddr(RTA_IFP, &rtm->rtm_addrs, &addrs, (struct sockaddr *)&sdl);
if ((rlen = write(af == AF_INET ? v4_sock : v6_sock,
rtbuf, rtm->rtm_msglen)) < 0) {
nlog(LOG_ERR, "nwamd_add_route: "
"got error %s writing to routing socket", strerror(errno));
} else if (rlen < rtm->rtm_msglen) {
nlog(LOG_ERR, "nwamd_add_route: "
"only wrote %d bytes of %d to routing socket\n",
rlen, rtm->rtm_msglen);
}
}
static char *
printaddr(void **address)
{
static char buffer[80];
sa_family_t family = *(sa_family_t *)*address;
struct sockaddr_in *s4 = *address;
struct sockaddr_in6 *s6 = *address;
struct sockaddr_dl *dl = *address;
switch (family) {
case AF_UNSPEC:
(void) inet_ntop(AF_UNSPEC, &s4->sin_addr, buffer,
sizeof (buffer));
*address = (char *)*address + sizeof (*s4);
break;
case AF_INET:
(void) inet_ntop(AF_INET, &s4->sin_addr, buffer,
sizeof (buffer));
*address = (char *)*address + sizeof (*s4);
break;
case AF_INET6:
(void) inet_ntop(AF_INET6, &s6->sin6_addr, buffer,
sizeof (buffer));
*address = (char *)*address + sizeof (*s6);
break;
case AF_LINK:
(void) snprintf(buffer, sizeof (buffer), "link %.*s",
dl->sdl_nlen, dl->sdl_data);
*address = (char *)*address + sizeof (*dl);
break;
default:
/*
* We can't reliably update the size of this thing
* because we don't know what its type is. So bump
* it by a sockaddr_in and see what happens. The
* caller should really make sure this never happens.
*/
*address = (char *)*address + sizeof (*s4);
(void) snprintf(buffer, sizeof (buffer),
"unknown address family %d", family);
break;
}
return (buffer);
}
static void
printaddrs(int mask, void *address)
{
if (mask == 0)
return;
if (mask & RTA_DST)
nlog(LOG_DEBUG, "destination address: %s", printaddr(&address));
if (mask & RTA_GATEWAY)
nlog(LOG_DEBUG, "gateway address: %s", printaddr(&address));
if (mask & RTA_NETMASK)
nlog(LOG_DEBUG, "netmask: %s", printaddr(&address));
if (mask & RTA_GENMASK)
nlog(LOG_DEBUG, "cloning mask: %s", printaddr(&address));
if (mask & RTA_IFP)
nlog(LOG_DEBUG, "interface name: %s", printaddr(&address));
if (mask & RTA_IFA)
nlog(LOG_DEBUG, "interface address: %s", printaddr(&address));
if (mask & RTA_AUTHOR)
nlog(LOG_DEBUG, "author: %s", printaddr(&address));
if (mask & RTA_BRD)
nlog(LOG_DEBUG, "broadcast address: %s", printaddr(&address));
}
static void
nextaddr(void **address)
{
sa_family_t family = *(sa_family_t *)*address;
switch (family) {
case AF_UNSPEC:
case AF_INET:
*address = (char *)*address + sizeof (struct sockaddr_in);
break;
case AF_INET6:
*address = (char *)*address + sizeof (struct sockaddr_in6);
break;
case AF_LINK:
*address = (char *)*address + sizeof (struct sockaddr_dl);
break;
default:
nlog(LOG_ERR, "unknown af (%d) while parsing rtm", family);
break;
}
}
static void *
getaddr(int addrid, int mask, void *addresses)
{
int i;
void *p = addresses;
if ((mask & addrid) == 0)
return (NULL);
for (i = 1; i < addrid; i <<= 1) {
if (i & mask)
nextaddr(&p);
}
return (p);
}
static void
setaddr(int addrid, int *maskp, void *addressesp, struct sockaddr *address)
{
struct sockaddr *p = *((struct sockaddr **)addressesp);
*maskp |= addrid;
switch (address->sa_family) {
case AF_INET:
(void) memcpy(p, address, sizeof (struct sockaddr_in));
break;
case AF_INET6:
(void) memcpy(p, address, sizeof (struct sockaddr_in6));
break;
case AF_LINK:
(void) memcpy(p, address, sizeof (struct sockaddr_dl));
break;
default:
nlog(LOG_ERR, "setaddr: unknown af (%d) while setting addr",
address->sa_family);
break;
}
nextaddr(addressesp);
}