/* rcs tags go here */ /* Original author: Bruce M. Simpson */ /*** This file is part of avahi. avahi is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. avahi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with avahi; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iface.h" #ifndef IN_LINKLOCAL #define IN_LINKLOCAL(i) (((u_int32_t)(i) & (0xffff0000)) == (0xa9fe0000)) #endif #ifndef elementsof #define elementsof(array) (sizeof(array)/sizeof(array[0])) #endif #ifndef so_set_nonblock #define so_set_nonblock(s, val) \ do { \ int __flags; \ __flags = fcntl((s), F_GETFL); \ if (__flags == -1) \ break; \ if (val != 0) \ __flags |= O_NONBLOCK; \ else \ __flags &= ~O_NONBLOCK; \ (void)fcntl((s), F_SETFL, __flags); \ } while (0) #endif #define MAX_RTMSG_SIZE 2048 struct rtm_dispinfo { u_char *di_buf; ssize_t di_buflen; ssize_t di_len; }; union rtmunion { struct rt_msghdr rtm; struct if_msghdr ifm; struct ifa_msghdr ifam; struct ifma_msghdr ifmam; struct if_announcemsghdr ifan; }; typedef union rtmunion rtmunion_t; struct Address; typedef struct Address Address; struct Address { in_addr_t address; AVAHI_LLIST_FIELDS(Address, addresses); }; static int rtm_dispatch(void); static int rtm_dispatch_newdeladdr(struct rtm_dispinfo *di); static int rtm_dispatch_ifannounce(struct rtm_dispinfo *di); static struct sockaddr *next_sa(struct sockaddr *sa); static int fd = -1; static int ifindex = -1; static AVAHI_LLIST_HEAD(Address, addresses) = NULL; int iface_init(int idx) { fd = socket(PF_ROUTE, SOCK_RAW, 0); if (fd == -1) { daemon_log(LOG_ERR, "socket(PF_ROUTE): %s", strerror(errno)); return (-1); } so_set_nonblock(fd, 1); ifindex = idx; return (fd); } int iface_get_initial_state(State *state) { int mib[6]; char *buf; struct if_msghdr *ifm; struct ifa_msghdr *ifam; char *lim; char *next; struct sockaddr *sa; size_t len; int naddrs; assert(state != NULL); assert(fd != -1); naddrs = 0; mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[2] = 0; mib[3] = 0; mib[4] = NET_RT_IFLIST; mib[5] = ifindex; if (sysctl(mib, elementsof(mib), NULL, &len, NULL, 0) != 0) { daemon_log(LOG_ERR, "sysctl(NET_RT_IFLIST): %s", strerror(errno)); return (-1); } buf = malloc(len); if (buf == NULL) { daemon_log(LOG_ERR, "malloc(%d): %s", len, strerror(errno)); return (-1); } if (sysctl(mib, elementsof(mib), buf, &len, NULL, 0) != 0) { daemon_log(LOG_ERR, "sysctl(NET_RT_IFLIST): %s", strerror(errno)); free(buf); return (-1); } lim = buf + len; for (next = buf; next < lim; next += ifm->ifm_msglen) { ifm = (struct if_msghdr *)next; if (ifm->ifm_type == RTM_NEWADDR) { ifam = (struct ifa_msghdr *)next; sa = (struct sockaddr *)(ifam + 1); if (sa->sa_family != AF_INET) continue; ++naddrs; } } free(buf); *state = (naddrs > 0) ? STATE_SLEEPING : STATE_START; return (0); } int iface_process(Event *event) { int b; assert(fd != -1); b = !!addresses; if (rtm_dispatch() == -1) return (-1); if (b && !addresses) *event = EVENT_ROUTABLE_ADDR_UNCONFIGURED; else if (!b && addresses) *event = EVENT_ROUTABLE_ADDR_CONFIGURED; return (0); } void iface_done(void) { Address *a; if (fd != -1) { close(fd); fd = -1; } while ((a = addresses) != NULL) { AVAHI_LLIST_REMOVE(Address, addresses, addresses, a); avahi_free(a); } } /* * Dispatch kernel routing socket messages. */ static int rtm_dispatch(void) { struct msghdr mh; struct iovec iov[1]; struct rt_msghdr *rtm; struct rtm_dispinfo *di; ssize_t len; int retval; di = malloc(sizeof(*di)); if (di == NULL) { daemon_log(LOG_ERR, "malloc(%d): %s", sizeof(*di), strerror(errno)); return (-1); } di->di_buflen = MAX_RTMSG_SIZE; di->di_buf = calloc(MAX_RTMSG_SIZE, 1); if (di->di_buf == NULL) { free(di); daemon_log(LOG_ERR, "calloc(%d): %s", MAX_RTMSG_SIZE, strerror(errno)); return (-1); } memset(&mh, 0, sizeof(mh)); iov[0].iov_base = di->di_buf; iov[0].iov_len = di->di_buflen; mh.msg_iov = iov; mh.msg_iovlen = 1; retval = 0; for (;;) { len = recvmsg(fd, &mh, MSG_DONTWAIT); if (len == -1) { if (errno == EWOULDBLOCK) break; else { daemon_log(LOG_ERR, "recvmsg(): %s", strerror(errno)); retval = -1; break; } } rtm = (void *)di->di_buf; if (rtm->rtm_version != RTM_VERSION) { daemon_log(LOG_ERR, "unknown routing socket message (version %d)\n", rtm->rtm_version); /* this is non-fatal; just ignore it for now. */ continue; } switch (rtm->rtm_type) { case RTM_NEWADDR: case RTM_DELADDR: retval = rtm_dispatch_newdeladdr(di); break; case RTM_IFANNOUNCE: retval = rtm_dispatch_ifannounce(di); break; default: break; } /* * If we got an error; assume our position on the call * stack is enclosed by a level-triggered event loop, * and signal the error condition. */ if (retval != 0) break; } free(di->di_buf); free(di); return (retval); } /* handle link coming or going away */ static int rtm_dispatch_ifannounce(struct rtm_dispinfo *di) { rtmunion_t *rtm = (void *)di->di_buf; assert(rtm->rtm.rtm_type == RTM_IFANNOUNCE); switch (rtm->ifan.ifan_what) { case IFAN_ARRIVAL: if (rtm->ifan.ifan_index == ifindex) { daemon_log(LOG_ERR, "RTM_IFANNOUNCE IFAN_ARRIVAL, for ifindex %d, which we already manage.", ifindex); return (-1); } break; case IFAN_DEPARTURE: if (rtm->ifan.ifan_index == ifindex) { daemon_log(LOG_ERR, "Interface vanished."); return (-1); } break; default: /* ignore */ break; } return (0); } static struct sockaddr * next_sa(struct sockaddr *sa) { void *p; size_t sa_size; sa_size = sa->sa_len; if (sa_size < sizeof(u_long)) sa_size = sizeof(u_long); p = ((char *)sa) + sa_size; return (struct sockaddr *)p; } /* handle address coming or going away */ static int rtm_dispatch_newdeladdr(struct rtm_dispinfo *di) { Address *ap; rtmunion_t *rtm; struct sockaddr *sa; struct sockaddr_in *sin; /* macro to skip to next RTA; has side-effects */ #define SKIPRTA(rtmsgp, rta, sa) \ do { \ if ((rtmsgp)->rtm_addrs & (rta)) \ (sa) = next_sa((sa)); \ } while (0) rtm = (void *)di->di_buf; assert(rtm->rtm.rtm_type == RTM_NEWADDR || rtm->rtm.rtm_type == RTM_DELADDR); if (rtm->rtm.rtm_index != ifindex) return (0); if (!(rtm->rtm.rtm_addrs & RTA_IFA)) { daemon_log(LOG_ERR, "ifa msg has no RTA_IFA."); return (0); } /* skip over rtmsg padding correctly */ sa = (struct sockaddr *)((&rtm->ifam) + 1); SKIPRTA(&rtm->rtm, RTA_DST, sa); SKIPRTA(&rtm->rtm, RTA_GATEWAY, sa); SKIPRTA(&rtm->rtm, RTA_NETMASK, sa); SKIPRTA(&rtm->rtm, RTA_GENMASK, sa); SKIPRTA(&rtm->rtm, RTA_IFP, sa); /* * sa now points to RTA_IFA sockaddr; we are only interested * in updates for link-local addresses. */ if (sa->sa_family != AF_INET) return (0); sin = (struct sockaddr_in *)sa; if (!IN_LINKLOCAL(ntohl(sin->sin_addr.s_addr))) return (0); for (ap = addresses; ap; ap = ap->addresses_next) { if (ap->address == sin->sin_addr.s_addr) break; } if (rtm->rtm.rtm_type == RTM_DELADDR && ap != NULL) { AVAHI_LLIST_REMOVE(Address, addresses, addresses, ap); avahi_free(ap); } if (rtm->rtm.rtm_type == RTM_NEWADDR && ap == NULL) { ap = avahi_new(Address, 1); ap->address = sin->sin_addr.s_addr; AVAHI_LLIST_PREPEND(Address, addresses, addresses, ap); } return (0); #undef SKIPRTA }