/* $USAGI: ninfod_core.c,v 1.29 2003-07-16 09:49:01 yoshfuji Exp $ */
/*
* Copyright (C) 2002 USAGI/WIDE Project.
* 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.
* 3. Neither the name of the project nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
*/
/*
* Author:
* YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#if HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#if STDC_HEADERS
# include <stdio.h>
# include <stdlib.h>
# include <stddef.h>
#else
# if HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif
#if ENABLE_THREADS && HAVE_PTHREAD_H
# include <pthread.h>
#endif
#if HAVE_STRING_H
# if !STDC_HEADERS && HAVE_MEMORY_H
# include <memory.h>
# endif
# include <string.h>
#endif
#if HAVE_STRINGS_H
# include <strings.h>
#endif
#if HAVE_INTTYPES_H
# include <inttypes.h>
#else
# if HAVE_STDINT_H
# include <stdint.h>
# endif
#endif
#if HAVE_UNISTD_H
# include <unistd.h>
#endif
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#if HAVE_SYS_UIO_H
#include <sys/uio.h>
#endif
#if HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#if HAVE_NETINET_ICMP6_H
# include <netinet/icmp6.h>
#endif
#ifndef HAVE_STRUCT_ICMP6_NODEINFO
# include "icmp6_nodeinfo.h"
#endif
#if HAVE_NETDB_H
# include <netdb.h>
#endif
#include <errno.h>
#if HAVE_SYSLOG_H
# include <syslog.h>
#endif
#include "ninfod.h"
#ifndef offsetof
# define offsetof(aggregate,member) ((size_t)&((aggregate *)0)->member)
#endif
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
/* ---------- */
/* ID */
static char *RCSID __attribute__ ((unused)) = "$USAGI: ninfod_core.c,v 1.29 2003-07-16 09:49:01 yoshfuji Exp $";
/* Variables */
int initialized = 0;
#if ENABLE_THREADS && HAVE_LIBPTHREAD
pthread_attr_t pattr;
#endif
static uint32_t suptypes[(MAX_SUPTYPES+31)>>5];
static size_t suptypes_len;
/* ---------- */
struct subjinfo {
uint8_t code;
char *name;
int (*checksubj)(CHECKANDFILL_ARGS);
int (*init)(INIT_ARGS);
};
static struct subjinfo subjinfo_table [] = {
[ICMP6_NI_SUBJ_IPV6] = {
.code = ICMP6_NI_SUBJ_IPV6,
.name = "IPv6",
//.init = init_nodeinfo_ipv6addr,
.checksubj = pr_nodeinfo_ipv6addr,
},
[ICMP6_NI_SUBJ_FQDN] = {
.code = ICMP6_NI_SUBJ_FQDN,
.name = "FQDN",
//.init = init_nodeinfo_nodename,
.checksubj = pr_nodeinfo_nodename,
},
[ICMP6_NI_SUBJ_IPV4] = {
.code = ICMP6_NI_SUBJ_IPV4,
.name = "IPv4",
//.init = init_nodeinfo_ipv4addr,
.checksubj = pr_nodeinfo_ipv4addr,
},
};
static struct subjinfo subjinfo_null = {
.name = "null",
.checksubj = pr_nodeinfo_noop,
};
static __inline__ struct subjinfo *subjinfo_lookup(int code)
{
if (code >= ARRAY_SIZE(subjinfo_table))
return NULL;
if (subjinfo_table[code].name == NULL)
return NULL;
return &subjinfo_table[code];
}
/* ---------- */
#define QTYPEINFO_F_RATELIMIT 0x1
struct qtypeinfo {
uint16_t qtype;
char *name;
int (*getreply)(CHECKANDFILL_ARGS);
void (*init)(INIT_ARGS);
int flags;
};
static struct qtypeinfo qtypeinfo_table[] = {
[NI_QTYPE_NOOP] = {
.qtype = NI_QTYPE_NOOP,
.name = "NOOP",
.getreply = pr_nodeinfo_noop,
},
#if ENABLE_SUPTYPES
[NI_QTYPE_SUPTYPES] = {
.qtype = NI_QTYPE_SUPTYPES,
.name = "SupTypes",
.getreply = pr_nodeinfo_suptypes,
.init = init_nodeinfo_suptypes,
},
#endif
[NI_QTYPE_DNSNAME] = {
.qtype = NI_QTYPE_DNSNAME,
.name = "DnsName",
.getreply = pr_nodeinfo_nodename,
.init = init_nodeinfo_nodename,
},
[NI_QTYPE_NODEADDR] = {
.qtype = NI_QTYPE_NODEADDR,
.name = "NodeAddr",
.getreply = pr_nodeinfo_ipv6addr,
.init = init_nodeinfo_ipv6addr,
},
[NI_QTYPE_IPV4ADDR] = {
.qtype = NI_QTYPE_IPV4ADDR,
.name = "IPv4Addr",
.getreply = pr_nodeinfo_ipv4addr,
.init = init_nodeinfo_ipv4addr,
},
};
static struct qtypeinfo qtypeinfo_unknown = {
.name = "unknown",
.getreply = pr_nodeinfo_unknown,
.flags = QTYPEINFO_F_RATELIMIT,
};
static struct qtypeinfo qtypeinfo_refused = {
.name = "refused",
.getreply = pr_nodeinfo_refused,
.flags = QTYPEINFO_F_RATELIMIT,
};
static __inline__ struct qtypeinfo *qtypeinfo_lookup(int qtype)
{
if (qtype >= ARRAY_SIZE(qtypeinfo_table))
return &qtypeinfo_unknown;
if (qtypeinfo_table[qtype].name == NULL)
return &qtypeinfo_unknown;
return &qtypeinfo_table[qtype];
}
/* ---------- */
/* noop */
int pr_nodeinfo_noop(CHECKANDFILL_ARGS)
{
DEBUG(LOG_DEBUG, "%s()\n", __func__);
if (subjlen) {
DEBUG(LOG_WARNING,
"%s(): invalid subject length(%zu)\n",
__func__, subjlen);
return 1;
}
if (reply) {
p->reply.ni_type = ICMP6_NI_REPLY;
p->reply.ni_code = ICMP6_NI_SUCCESS;
p->reply.ni_cksum = 0;
p->reply.ni_qtype = htons(NI_QTYPE_NOOP);
p->reply.ni_flags = flags;
}
if (subj_if)
*subj_if = 0;
return 0;
}
#if ENABLE_SUPTYPES
/* suptypes */
int pr_nodeinfo_suptypes(CHECKANDFILL_ARGS)
{
DEBUG(LOG_DEBUG, "%s()\n", __func__);
if (subjlen) {
DEBUG(LOG_WARNING, "%s(): invalid subject length(%zu)\n",
__func__, subjlen);
return 1;
}
if (reply) {
p->reply.ni_type = ICMP6_NI_REPLY;
p->reply.ni_code = ICMP6_NI_SUCCESS;
p->reply.ni_cksum = 0;
p->reply.ni_qtype = htons(NI_QTYPE_SUPTYPES);
p->reply.ni_flags = flags&~NI_SUPTYPE_FLAG_COMPRESS;
p->replydatalen = suptypes_len<<2;
p->replydata = ni_malloc(p->replydatalen);
if (p->replydata == NULL) {
p->replydatalen = -1;
return -1; /*XXX*/
}
memcpy(p->replydata, suptypes, p->replydatalen);
}
return 0;
}
void init_nodeinfo_suptypes(INIT_ARGS)
{
size_t w, b;
int i;
if (!forced && initialized)
return;
memset(suptypes, 0, sizeof(suptypes));
suptypes_len = 0;
for (i=0; i < ARRAY_SIZE(qtypeinfo_table); i++) {
unsigned short qtype;
if (qtypeinfo_table[i].name == NULL)
continue;
qtype = qtypeinfo_table[i].qtype;
w = qtype>>5;
b = qtype&0x1f;
if (w >= ARRAY_SIZE(suptypes)) {
/* This is programming error. */
DEBUG(LOG_ERR, "Warning: Too Large Supported Types\n");
exit(1);
}
suptypes[w] |= htonl(1<<b);
if (suptypes_len < w)
suptypes_len = w;
}
suptypes_len++;
}
#endif
/* ---------- */
/* unknown qtype response */
int pr_nodeinfo_unknown(CHECKANDFILL_ARGS)
{
if (!reply)
return -1; /*???*/
p->reply.ni_type = ICMP6_NI_REPLY;
p->reply.ni_code = ICMP6_NI_UNKNOWN;
p->reply.ni_cksum = 0;
//p->reply.ni_qtype = 0;
p->reply.ni_flags = flags;
p->replydata = NULL;
p->replydatalen = 0;
return 0;
}
/* refused response */
int pr_nodeinfo_refused(CHECKANDFILL_ARGS)
{
if (!reply)
return -1; /*???*/
p->reply.ni_type = ICMP6_NI_REPLY;
p->reply.ni_code = ICMP6_NI_REFUSED;
p->reply.ni_cksum = 0;
//p->reply.ni_qtype = 0;
p->reply.ni_flags = flags;
p->replydata = NULL;
p->replydatalen = 0;
return 0;
}
/* ---------- */
/* Policy */
static int ni_policy(struct packetcontext *p)
{
const struct in6_addr *saddr = &((const struct sockaddr_in6 *)&p->addr)->sin6_addr;
/*
* >0: reply
* 0: refused
* <0: discard
*/
/* Default policy is to refuse queries from
* non-local addresses; loopback, link-local or
* site-local are okay
*/
if (!(IN6_IS_ADDR_LINKLOCAL(saddr) ||
IN6_IS_ADDR_SITELOCAL(saddr) ||
IN6_IS_ADDR_LOOPBACK(saddr)))
return 0;
return 1;
}
/* ---------- */
void init_core(int forced)
{
int i;
DEBUG(LOG_DEBUG, "%s()\n", __func__);
if (!initialized || forced) {
struct timeval tv;
unsigned int seed = 0;
pid_t pid;
if (gettimeofday(&tv, NULL) < 0) {
DEBUG(LOG_WARNING, "%s(): failed to gettimeofday()\n", __func__);
} else {
seed = (tv.tv_usec & 0xffffffff);
}
pid = getpid();
seed ^= (((unsigned long)pid) & 0xffffffff);
srand(seed);
#if ENABLE_THREADS && HAVE_LIBPTHREAD
if (initialized)
pthread_attr_destroy(&pattr);
pthread_attr_init(&pattr);
pthread_attr_setdetachstate(&pattr, PTHREAD_CREATE_DETACHED);
#endif
}
for (i=0; i < ARRAY_SIZE(subjinfo_table); i++) {
if (subjinfo_table[i].name == NULL)
continue;
if (subjinfo_table[i].init)
subjinfo_table[i].init(forced);
}
for (i=0; i < ARRAY_SIZE(qtypeinfo_table); i++) {
if (qtypeinfo_table[i].name == NULL)
continue;
if (qtypeinfo_table[i].init)
qtypeinfo_table[i].init(forced);
}
initialized = 1;
return;
}
#if ENABLE_THREADS && HAVE_LIBPTHREAD
static void *ni_send_thread(void *data)
{
int ret;
DEBUG(LOG_DEBUG, "%s(): thread=%ld\n", __func__, pthread_self());
ret = ni_send(data);
DEBUG(LOG_DEBUG, "%s(): thread=%ld => %d\n", __func__, pthread_self(), ret);
return NULL;
}
#else
static int ni_send_fork(struct packetcontext *p)
{
pid_t child = fork();
if (child < 0)
return -1;
if (child == 0) {
pid_t grandchild = fork();
if (grandchild < 0)
exit(1);
if (grandchild == 0) {
int ret;
DEBUG(LOG_DEBUG, "%s(): worker=%d\n",
__func__, getpid());
ret = ni_send(p);
DEBUG(LOG_DEBUG, "%s(): worker=%d => %d\n",
__func__, getpid(), ret);
exit(ret > 0 ? 1 : 0);
}
ni_free(p->replydata);
ni_free(p);
exit(0);
} else {
waitpid(child, NULL, 0);
ni_free(p->replydata);
ni_free(p);
}
return 0;
}
#endif
static int ni_ratelimit(void)
{
static struct timeval last;
struct timeval tv, sub;
if (gettimeofday(&tv, NULL) < 0) {
DEBUG(LOG_WARNING, "%s(): gettimeofday(): %s\n",
__func__, strerror(errno));
return -1;
}
if (!timerisset(&last)) {
last = tv;
return 0;
}
timersub(&tv, &last, &sub);
if (sub.tv_sec < 1)
return 1;
last = tv;
return 0;
}
int pr_nodeinfo(struct packetcontext *p)
{
struct icmp6_nodeinfo *query = (struct icmp6_nodeinfo *)p->query;
char *subject = (char *)(query + 1);
size_t subjlen;
struct subjinfo *subjinfo;
struct qtypeinfo *qtypeinfo;
int replyonsubjcheck = 0;
unsigned int subj_if;
#if ENABLE_DEBUG
char printbuf[128];
int i;
char *cp;
#endif
#if ENABLE_THREADS && HAVE_PTHREAD_H
pthread_t thread;
#endif
int rc;
/* Step 0: Check destination address
* discard non-linklocal multicast
* discard non-nigroup multicast address(?)
*/
if (IN6_IS_ADDR_MULTICAST(&p->pktinfo.ipi6_addr)) {
if (!IN6_IS_ADDR_MC_LINKLOCAL(&p->pktinfo.ipi6_addr)) {
DEBUG(LOG_WARNING,
"Destination is non-link-local multicast address.\n");
ni_free(p);
return -1;
}
#if 0
/* Do not discard NI Queries to multicast address
* other than its own NI Group Address(es) by default.
*/
if (!check_nigroup(&p->pktinfo.ipi6_addr)) {
DEBUG(LOG_WARNING,
"Destination is link-local multicast address other than "
"NI Group address.\n");
ni_free(p);
return -1;
}
#endif
}
/* Step 1: Check length */
if (p->querylen < sizeof(struct icmp6_nodeinfo)) {
DEBUG(LOG_WARNING, "Query too short\n");
ni_free(p);
return -1;
}
#if ENABLE_DEBUG
cp = printbuf;
for (i = 0; i < sizeof(query->icmp6_ni_nonce); i++) {
cp += sprintf(cp, " %02x", query->icmp6_ni_nonce[i]);
}
DEBUG(LOG_DEBUG, "%s(): qtype=%d, flags=0x%04x, nonce[] = {%s }\n",
__func__,
ntohs(query->ni_qtype), ntohs(query->ni_flags), printbuf);
#endif
subjlen = p->querylen - sizeof(struct icmp6_nodeinfo);
/* Step 2: Check Subject Code */
switch(htons(query->ni_qtype)) {
case NI_QTYPE_NOOP:
case NI_QTYPE_SUPTYPES:
if (query->ni_code != ICMP6_NI_SUBJ_FQDN) {
DEBUG(LOG_WARNING,
"%s(): invalid/unknown code %u\n",
__func__, query->ni_code);
subjlen = 0;
}
subjinfo = &subjinfo_null;
break;
default:
subjinfo = subjinfo_lookup(query->ni_code);
if (!subjinfo) {
DEBUG(LOG_WARNING,
"%s(): unknown code %u\n",
__func__, query->ni_code);
ni_free(p);
return -1;
}
}
/* Step 3: Lookup Qtype */
qtypeinfo = qtypeinfo_lookup(ntohs(query->ni_qtype));
/* Step 4: Check Subject
* (And fill reply if it is available now)
*/
if (qtypeinfo->getreply == subjinfo->checksubj)
replyonsubjcheck = 1;
if (subjinfo->checksubj(p,
subject, subjlen,
query->ni_flags,
replyonsubjcheck ? NULL : &subj_if,
replyonsubjcheck)) {
if (p->replydatalen < 0) {
DEBUG(LOG_WARNING,
"failed to make reply: %s\n",
strerror(errno));
}
ni_free(p);
return -1;
}
/* XXX: Step 5: Check the policy */
rc = ni_policy(p);
if (rc <= 0) {
ni_free(p->replydata);
p->replydata = NULL;
p->replydatalen = 0;
if (rc < 0) {
DEBUG(LOG_WARNING, "Ignored by policy.\n");
ni_free(p);
return -1;
}
DEBUG(LOG_WARNING, "Refused by policy.\n");
replyonsubjcheck = 0;
qtypeinfo = &qtypeinfo_refused;
}
/* Step 6: Fill the reply if not yet done */
if (!replyonsubjcheck) {
if (qtypeinfo->getreply(p,
NULL, 0,
query->ni_flags,
&subj_if,
1)) {
if (p->replydatalen) {
DEBUG(LOG_WARNING,
"failed to make reply: %s\n",
strerror(errno));
}
ni_free(p);
return -1;
}
}
/* Step 7: Rate Limit */
if (qtypeinfo->flags&QTYPEINFO_F_RATELIMIT &&
ni_ratelimit()) {
ni_free(p->replydata);
ni_free(p);
return -1;
}
/* Step 8: Fill Qtype / Nonce */
p->reply.ni_qtype = query->ni_qtype;
memcpy(p->reply.icmp6_ni_nonce, query->icmp6_ni_nonce, sizeof(p->reply.icmp6_ni_nonce));
/* Step 9: Source address selection */
if (IN6_IS_ADDR_MULTICAST(&p->pktinfo.ipi6_addr)) {
/* if query was sent to multicast address,
* use source address selection in kernel.
* XXX: anycast?
*/
memset(&p->pktinfo.ipi6_addr, 0, sizeof(p->pktinfo.ipi6_addr));
/* Random Delay between zero and MAX_ANYCAST_DELAY_TIME is
* required if query was sent to anycast or multicast address.
*/
p->delay = (int) (MAX_ANYCAST_DELAY_TIME*rand()/(RAND_MAX+1.0));
} else {
p->delay = 0;
}
/* Step 10: Send the reply
* XXX: with possible random delay */
#if ENABLE_THREADS && HAVE_LIBPTHREAD
/* ni_send_thread() frees p */
if (pthread_create(&thread, &pattr, ni_send_thread, p)) {
ni_free(p->replydata);
ni_free(p);
return -1;
}
#else
/* ni_send_fork() frees p */
if (ni_send_fork(p)) {
ni_free(p->replydata);
ni_free(p);
return -1;
}
#endif
return 0;
}