/* $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; }