/* $OpenBSD: kroute.c,v 1.12 2015/09/27 17:29:46 stsp Exp $ */ /* * Copyright (c) 2004 Esben Norby * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dvmrp.h" #include "dvmrpd.h" #include "log.h" struct { u_int32_t rtseq; pid_t pid; struct event ev; } kr_state; struct kif_node { RB_ENTRY(kif_node) entry; struct kif k; }; int kif_compare(struct kif_node *, struct kif_node *); struct kif_node *kif_find(int); int kif_insert(struct kif_node *); int kif_remove(struct kif_node *); void kif_clear(void); in_addr_t prefixlen2mask(u_int8_t); void get_rtaddrs(int, struct sockaddr *, struct sockaddr **); void if_change(u_short, int, struct if_data *); void if_announce(void *); int fetchifs(int); RB_HEAD(kif_tree, kif_node) kit; RB_PROTOTYPE(kif_tree, kif_node, entry, kif_compare) RB_GENERATE(kif_tree, kif_node, entry, kif_compare) int kif_init(void) { RB_INIT(&kit); if (fetchifs(0) == -1) return (-1); return (0); } int kr_init(void) { int opt, fd; if ((fd = socket(AF_ROUTE, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) == -1) { log_warn("kr_init: socket"); return (-1); } /* not interested in my own messages */ if (setsockopt(fd, SOL_SOCKET, SO_USELOOPBACK, &opt, sizeof(opt)) == -1) log_warn("kr_init: setsockopt"); /* not fatal */ kr_state.pid = getpid(); kr_state.rtseq = 1; event_set(&kr_state.ev, fd, EV_READ | EV_PERSIST, kr_dispatch_msg, NULL); event_add(&kr_state.ev, NULL); return (0); } void kr_shutdown(void) { kif_clear(); close(EVENT_FD((&kr_state.ev))); } void kr_ifinfo(char *ifname) { struct kif_node *kif; RB_FOREACH(kif, kif_tree, &kit) if (!strcmp(ifname, kif->k.ifname)) { main_imsg_compose_dvmrpe(IMSG_CTL_IFINFO, 0, &kif->k, sizeof(kif->k)); return; } } /* rb-tree compare */ int kif_compare(struct kif_node *a, struct kif_node *b) { return (b->k.ifindex - a->k.ifindex); } struct kif_node * kif_find(int ifindex) { struct kif_node s; bzero(&s, sizeof(s)); s.k.ifindex = ifindex; return (RB_FIND(kif_tree, &kit, &s)); } struct kif * kif_findname(char *ifname) { struct kif_node *kif; RB_FOREACH(kif, kif_tree, &kit) if (!strcmp(ifname, kif->k.ifname)) return (&kif->k); return (NULL); } int kif_insert(struct kif_node *kif) { if (RB_INSERT(kif_tree, &kit, kif) != NULL) { log_warnx("RB_INSERT(kif_tree, &kit, kif)"); free(kif); return (-1); } return (0); } int kif_remove(struct kif_node *kif) { if (RB_REMOVE(kif_tree, &kit, kif) == NULL) { log_warnx("RB_REMOVE(kif_tree, &kit, kif)"); return (-1); } free(kif); return (0); } void kif_clear(void) { struct kif_node *kif; while ((kif = RB_MIN(kif_tree, &kit)) != NULL) kif_remove(kif); } /* misc */ u_int8_t prefixlen_classful(in_addr_t ina) { /* it hurt to write this. */ if (ina >= 0xf0000000U) /* class E */ return (32); else if (ina >= 0xe0000000U) /* class D */ return (4); else if (ina >= 0xc0000000U) /* class C */ return (24); else if (ina >= 0x80000000U) /* class B */ return (16); else /* class A */ return (8); } u_int8_t mask2prefixlen(in_addr_t ina) { if (ina == 0) return (0); else return (33 - ffs(ntohl(ina))); } in_addr_t prefixlen2mask(u_int8_t prefixlen) { if (prefixlen == 0) return (0); return (0xffffffff << (32 - prefixlen)); } void if_change(u_short ifindex, int flags, struct if_data *ifd) { struct kif_node *kif; u_int8_t reachable; if ((kif = kif_find(ifindex)) == NULL) { log_warnx("interface with index %u not found", ifindex); return; } kif->k.flags = flags; kif->k.link_state = ifd->ifi_link_state; kif->k.if_type = ifd->ifi_type; kif->k.baudrate = ifd->ifi_baudrate; if ((reachable = (flags & IFF_UP) && LINK_STATE_IS_UP(ifd->ifi_link_state)) == kif->k.nh_reachable) return; /* nothing changed wrt nexthop validity */ kif->k.nh_reachable = reachable; main_imsg_compose_dvmrpe(IMSG_IFINFO, 0, &kif->k, sizeof(kif->k)); } void if_announce(void *msg) { struct if_announcemsghdr *ifan; struct kif_node *kif; ifan = msg; switch (ifan->ifan_what) { case IFAN_ARRIVAL: if ((kif = calloc(1, sizeof(struct kif_node))) == NULL) { log_warn("if_announce"); return; } kif->k.ifindex = ifan->ifan_index; strlcpy(kif->k.ifname, ifan->ifan_name, sizeof(kif->k.ifname)); kif_insert(kif); break; case IFAN_DEPARTURE: kif = kif_find(ifan->ifan_index); kif_remove(kif); break; } } /* rtsock */ #define ROUNDUP(a, size) \ (((a) & ((size) - 1)) ? (1 + ((a) | ((size) - 1))) : (a)) void get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info) { int i; for (i = 0; i < RTAX_MAX; i++) { if (addrs & (1 << i)) { rti_info[i] = sa; sa = (struct sockaddr *)((char *)(sa) + ROUNDUP(sa->sa_len, sizeof(long))); } else rti_info[i] = NULL; } } int fetchifs(int ifindex) { size_t len; int mib[6]; char *buf, *next, *lim; struct if_msghdr ifm; struct kif_node *kif; struct sockaddr *sa, *rti_info[RTAX_MAX]; struct sockaddr_dl *sdl; mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[2] = 0; mib[3] = AF_INET; mib[4] = NET_RT_IFLIST; mib[5] = ifindex; if (sysctl(mib, 6, NULL, &len, NULL, 0) == -1) { log_warn("sysctl"); return (-1); } if ((buf = malloc(len)) == NULL) { log_warn("fetchif"); return (-1); } if (sysctl(mib, 6, buf, &len, NULL, 0) == -1) { log_warn("sysctl"); free(buf); return (-1); } lim = buf + len; for (next = buf; next < lim; next += ifm.ifm_msglen) { memcpy(&ifm, next, sizeof(ifm)); sa = (struct sockaddr *)(next + sizeof(ifm)); get_rtaddrs(ifm.ifm_addrs, sa, rti_info); if (ifm.ifm_version != RTM_VERSION) continue; if (ifm.ifm_type != RTM_IFINFO) continue; if ((kif = calloc(1, sizeof(struct kif_node))) == NULL) { log_warn("fetchifs"); free(buf); return (-1); } kif->k.ifindex = ifm.ifm_index; kif->k.flags = ifm.ifm_flags; kif->k.link_state = ifm.ifm_data.ifi_link_state; kif->k.if_type = ifm.ifm_data.ifi_type; kif->k.baudrate = ifm.ifm_data.ifi_baudrate; kif->k.mtu = ifm.ifm_data.ifi_mtu; kif->k.nh_reachable = (kif->k.flags & IFF_UP) && LINK_STATE_IS_UP(ifm.ifm_data.ifi_link_state); if ((sa = rti_info[RTAX_IFP]) != NULL) if (sa->sa_family == AF_LINK) { sdl = (struct sockaddr_dl *)sa; if (sdl->sdl_nlen >= sizeof(kif->k.ifname)) memcpy(kif->k.ifname, sdl->sdl_data, sizeof(kif->k.ifname) - 1); else if (sdl->sdl_nlen > 0) memcpy(kif->k.ifname, sdl->sdl_data, sdl->sdl_nlen); /* string already terminated via calloc() */ } kif_insert(kif); } free(buf); return (0); } void kr_dispatch_msg(int fd, short event, void *bula) { char buf[RT_BUF_SIZE]; ssize_t n; char *next, *lim; struct rt_msghdr *rtm; struct if_msghdr ifm; if ((n = read(fd, &buf, sizeof(buf))) == -1) { if (errno == EAGAIN || errno == EINTR) return; fatal("dispatch_rtmsg: read error"); } if (n == 0) fatalx("routing socket closed"); lim = buf + n; for (next = buf; next < lim; next += rtm->rtm_msglen) { rtm = (struct rt_msghdr *)next; if (lim < next + sizeof(u_short) || lim < next + rtm->rtm_msglen) fatalx("dispatch_rtmsg: partial rtm in buffer"); if (rtm->rtm_version != RTM_VERSION) continue; switch (rtm->rtm_type) { case RTM_IFINFO: memcpy(&ifm, next, sizeof(ifm)); if_change(ifm.ifm_index, ifm.ifm_flags, &ifm.ifm_data); break; case RTM_IFANNOUNCE: if_announce(next); break; default: /* ignore for now */ break; } } }