| /* $NetBSD: grabmyaddr.c,v 1.28 2011/03/14 17:18:12 tteras Exp $ */ |
| /* |
| * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. |
| * Copyright (C) 2008 Timo Teras <timo.teras@iki.fi>. |
| * 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. |
| */ |
| |
| #include "config.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/queue.h> |
| #include <sys/socket.h> |
| |
| #ifdef __linux__ |
| #include <linux/netlink.h> |
| #include <linux/rtnetlink.h> |
| #define USE_NETLINK |
| #else |
| #include <net/route.h> |
| #include <net/if.h> |
| #include <net/if_dl.h> |
| #include <sys/sysctl.h> |
| #define USE_ROUTE |
| #endif |
| |
| #include "var.h" |
| #include "misc.h" |
| #include "vmbuf.h" |
| #include "plog.h" |
| #include "sockmisc.h" |
| #include "session.h" |
| #include "debug.h" |
| |
| #include "localconf.h" |
| #include "handler.h" |
| #include "grabmyaddr.h" |
| #include "sockmisc.h" |
| #include "isakmp_var.h" |
| #include "gcmalloc.h" |
| #include "nattraversal.h" |
| |
| static int kernel_receive __P((void *ctx, int fd)); |
| static int kernel_open_socket __P((void)); |
| static void kernel_sync __P((void)); |
| |
| struct myaddr { |
| LIST_ENTRY(myaddr) chain; |
| struct sockaddr_storage addr; |
| int fd; |
| int udp_encap; |
| }; |
| |
| static LIST_HEAD(_myaddr_list_, myaddr) configured, opened; |
| |
| static void |
| myaddr_delete(my) |
| struct myaddr *my; |
| { |
| if (my->fd != -1) |
| isakmp_close(my->fd); |
| LIST_REMOVE(my, chain); |
| racoon_free(my); |
| } |
| |
| static int |
| myaddr_configured(addr) |
| struct sockaddr *addr; |
| { |
| struct myaddr *cfg; |
| |
| if (LIST_EMPTY(&configured)) |
| return TRUE; |
| |
| LIST_FOREACH(cfg, &configured, chain) { |
| if (cmpsaddr(addr, (struct sockaddr *) &cfg->addr) <= CMPSADDR_WILDPORT_MATCH) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static int |
| myaddr_open(addr, udp_encap) |
| struct sockaddr *addr; |
| int udp_encap; |
| { |
| struct myaddr *my; |
| |
| /* Already open? */ |
| LIST_FOREACH(my, &opened, chain) { |
| if (cmpsaddr(addr, (struct sockaddr *) &my->addr) <= CMPSADDR_WILDPORT_MATCH) |
| return TRUE; |
| } |
| |
| my = racoon_calloc(1, sizeof(struct myaddr)); |
| if (my == NULL) |
| return FALSE; |
| |
| memcpy(&my->addr, addr, sysdep_sa_len(addr)); |
| my->fd = isakmp_open(addr, udp_encap); |
| if (my->fd < 0) { |
| racoon_free(my); |
| return FALSE; |
| } |
| my->udp_encap = udp_encap; |
| LIST_INSERT_HEAD(&opened, my, chain); |
| return TRUE; |
| } |
| |
| static int |
| myaddr_open_all_configured(addr) |
| struct sockaddr *addr; |
| { |
| /* create all configured, not already opened addresses */ |
| struct myaddr *cfg, *my; |
| |
| if (addr != NULL) { |
| switch (addr->sa_family) { |
| case AF_INET: |
| #ifdef INET6 |
| case AF_INET6: |
| #endif |
| break; |
| default: |
| return FALSE; |
| } |
| } |
| |
| LIST_FOREACH(cfg, &configured, chain) { |
| if (addr != NULL && |
| cmpsaddr(addr, (struct sockaddr *) &cfg->addr) > CMPSADDR_WILDPORT_MATCH) |
| continue; |
| if (!myaddr_open((struct sockaddr *) &cfg->addr, cfg->udp_encap)) |
| return FALSE; |
| } |
| if (LIST_EMPTY(&configured)) { |
| #ifdef ENABLE_HYBRID |
| /* Exclude any address we got through ISAKMP mode config */ |
| if (exclude_cfg_addr(addr) == 0) |
| return FALSE; |
| #endif |
| set_port(addr, lcconf->port_isakmp); |
| myaddr_open(addr, FALSE); |
| #ifdef ENABLE_NATT |
| set_port(addr, lcconf->port_isakmp_natt); |
| myaddr_open(addr, TRUE); |
| #endif |
| } |
| return TRUE; |
| } |
| |
| static void |
| myaddr_close_all_open(addr) |
| struct sockaddr *addr; |
| { |
| /* delete all matching open sockets */ |
| struct myaddr *my, *next; |
| |
| for (my = LIST_FIRST(&opened); my; my = next) { |
| next = LIST_NEXT(my, chain); |
| |
| if (cmpsaddr((struct sockaddr *) addr, |
| (struct sockaddr *) &my->addr) |
| <= CMPSADDR_WOP_MATCH) |
| myaddr_delete(my); |
| } |
| } |
| |
| static void |
| myaddr_flush_list(list) |
| struct _myaddr_list_ *list; |
| { |
| struct myaddr *my, *next; |
| |
| for (my = LIST_FIRST(list); my; my = next) { |
| next = LIST_NEXT(my, chain); |
| myaddr_delete(my); |
| } |
| } |
| |
| void |
| myaddr_flush() |
| { |
| myaddr_flush_list(&configured); |
| } |
| |
| int |
| myaddr_listen(addr, udp_encap) |
| struct sockaddr *addr; |
| int udp_encap; |
| { |
| struct myaddr *my; |
| |
| if (sysdep_sa_len(addr) > sizeof(my->addr)) { |
| plog(LLV_ERROR, LOCATION, NULL, |
| "sockaddr size larger than sockaddr_storage\n"); |
| return -1; |
| } |
| |
| my = racoon_calloc(1, sizeof(struct myaddr)); |
| if (my == NULL) |
| return -1; |
| |
| memcpy(&my->addr, addr, sysdep_sa_len(addr)); |
| my->udp_encap = udp_encap; |
| my->fd = -1; |
| LIST_INSERT_HEAD(&configured, my, chain); |
| |
| return 0; |
| } |
| |
| void |
| myaddr_sync() |
| { |
| struct myaddr *my, *next; |
| |
| if (!lcconf->strict_address) { |
| kernel_sync(); |
| |
| /* delete all existing listeners which are not configured */ |
| for (my = LIST_FIRST(&opened); my; my = next) { |
| next = LIST_NEXT(my, chain); |
| |
| if (!myaddr_configured((struct sockaddr *) &my->addr)) |
| myaddr_delete(my); |
| } |
| } |
| } |
| |
| int |
| myaddr_getfd(addr) |
| struct sockaddr *addr; |
| { |
| struct myaddr *my; |
| |
| LIST_FOREACH(my, &opened, chain) { |
| if (cmpsaddr((struct sockaddr *) &my->addr, addr) <= CMPSADDR_WILDPORT_MATCH) |
| return my->fd; |
| } |
| |
| return -1; |
| } |
| |
| int |
| myaddr_getsport(addr) |
| struct sockaddr *addr; |
| { |
| struct myaddr *my; |
| |
| LIST_FOREACH(my, &opened, chain) { |
| if (cmpsaddr((struct sockaddr *) &my->addr, addr) <= CMPSADDR_WILDPORT_MATCH) |
| return extract_port((struct sockaddr *) &my->addr); |
| } |
| |
| return PORT_ISAKMP; |
| } |
| |
| void |
| myaddr_init_lists() |
| { |
| LIST_INIT(&configured); |
| LIST_INIT(&opened); |
| } |
| |
| int |
| myaddr_init() |
| { |
| if (!lcconf->strict_address) { |
| lcconf->rtsock = kernel_open_socket(); |
| if (lcconf->rtsock < 0) |
| return -1; |
| monitor_fd(lcconf->rtsock, kernel_receive, NULL, 0); |
| } else { |
| lcconf->rtsock = -1; |
| if (!myaddr_open_all_configured(NULL)) |
| return -1; |
| } |
| return 0; |
| } |
| |
| void |
| myaddr_close() |
| { |
| myaddr_flush_list(&configured); |
| myaddr_flush_list(&opened); |
| if (lcconf->rtsock != -1) { |
| unmonitor_fd(lcconf->rtsock); |
| close(lcconf->rtsock); |
| } |
| } |
| |
| #if defined(USE_NETLINK) |
| |
| static int netlink_fd = -1; |
| |
| #define NLMSG_TAIL(nmsg) \ |
| ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) |
| |
| static void |
| parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) |
| { |
| memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); |
| while (RTA_OK(rta, len)) { |
| if (rta->rta_type <= max) |
| tb[rta->rta_type] = rta; |
| rta = RTA_NEXT(rta,len); |
| } |
| } |
| |
| static int |
| netlink_add_rtattr_l(struct nlmsghdr *n, int maxlen, int type, |
| const void *data, int alen) |
| { |
| int len = RTA_LENGTH(alen); |
| struct rtattr *rta; |
| |
| if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) |
| return FALSE; |
| |
| rta = NLMSG_TAIL(n); |
| rta->rta_type = type; |
| rta->rta_len = len; |
| memcpy(RTA_DATA(rta), data, alen); |
| n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); |
| return TRUE; |
| } |
| |
| static int |
| netlink_enumerate(fd, family, type) |
| int fd; |
| int family; |
| int type; |
| { |
| struct { |
| struct nlmsghdr nlh; |
| struct rtgenmsg g; |
| } req; |
| struct sockaddr_nl addr; |
| static __u32 seq = 0; |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.nl_family = AF_NETLINK; |
| |
| memset(&req, 0, sizeof(req)); |
| req.nlh.nlmsg_len = sizeof(req); |
| req.nlh.nlmsg_type = type; |
| req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; |
| req.nlh.nlmsg_pid = 0; |
| req.nlh.nlmsg_seq = ++seq; |
| req.g.rtgen_family = family; |
| |
| return sendto(fd, (void *) &req, sizeof(req), 0, |
| (struct sockaddr *) &addr, sizeof(addr)) >= 0; |
| } |
| |
| static void |
| netlink_add_del_address(int add, struct sockaddr *saddr) |
| { |
| plog(LLV_DEBUG, LOCATION, NULL, |
| "Netlink: address %s %s\n", |
| saddrwop2str((struct sockaddr *) saddr), |
| add ? "added" : "deleted"); |
| |
| if (add) |
| myaddr_open_all_configured(saddr); |
| else |
| myaddr_close_all_open(saddr); |
| } |
| |
| #ifdef INET6 |
| static int |
| netlink_process_addr(struct nlmsghdr *h) |
| { |
| struct sockaddr_storage addr; |
| struct ifaddrmsg *ifa; |
| struct rtattr *rta[IFA_MAX+1]; |
| struct sockaddr_in6 *sin6; |
| |
| ifa = NLMSG_DATA(h); |
| parse_rtattr(rta, IFA_MAX, IFA_RTA(ifa), IFA_PAYLOAD(h)); |
| |
| if (ifa->ifa_family != AF_INET6) |
| return 0; |
| if (ifa->ifa_flags & IFA_F_TENTATIVE) |
| return 0; |
| if (rta[IFA_LOCAL] == NULL) |
| rta[IFA_LOCAL] = rta[IFA_ADDRESS]; |
| if (rta[IFA_LOCAL] == NULL) |
| return 0; |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.ss_family = ifa->ifa_family; |
| sin6 = (struct sockaddr_in6 *) &addr; |
| memcpy(&sin6->sin6_addr, RTA_DATA(rta[IFA_LOCAL]), |
| sizeof(sin6->sin6_addr)); |
| if (!IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) |
| return 0; |
| sin6->sin6_scope_id = ifa->ifa_index; |
| |
| netlink_add_del_address(h->nlmsg_type == RTM_NEWADDR, |
| (struct sockaddr *) &addr); |
| |
| return 0; |
| } |
| #endif |
| |
| static int |
| netlink_route_is_local(int family, const unsigned char *addr, size_t addr_len) |
| { |
| struct { |
| struct nlmsghdr n; |
| struct rtmsg r; |
| char buf[1024]; |
| } req; |
| struct rtmsg *r = NLMSG_DATA(&req.n); |
| struct rtattr *rta[RTA_MAX+1]; |
| struct sockaddr_nl nladdr; |
| ssize_t rlen; |
| |
| memset(&req, 0, sizeof(req)); |
| req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); |
| req.n.nlmsg_flags = NLM_F_REQUEST; |
| req.n.nlmsg_type = RTM_GETROUTE; |
| req.r.rtm_family = family; |
| netlink_add_rtattr_l(&req.n, sizeof(req), RTA_DST, |
| addr, addr_len); |
| req.r.rtm_dst_len = addr_len * 8; |
| |
| memset(&nladdr, 0, sizeof(nladdr)); |
| nladdr.nl_family = AF_NETLINK; |
| |
| if (sendto(netlink_fd, &req, sizeof(req), 0, |
| (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) |
| return 0; |
| rlen = recv(netlink_fd, &req, sizeof(req), 0); |
| if (rlen < 0) |
| return 0; |
| |
| return req.n.nlmsg_type == RTM_NEWROUTE && |
| req.r.rtm_type == RTN_LOCAL; |
| } |
| |
| static int |
| netlink_process_route(struct nlmsghdr *h) |
| { |
| struct sockaddr_storage addr; |
| struct rtmsg *rtm; |
| struct rtattr *rta[RTA_MAX+1]; |
| struct sockaddr_in *sin; |
| #ifdef INET6 |
| struct sockaddr_in6 *sin6; |
| #endif |
| |
| rtm = NLMSG_DATA(h); |
| |
| /* local IP addresses get local route in the local table */ |
| if (rtm->rtm_type != RTN_LOCAL || |
| rtm->rtm_table != RT_TABLE_LOCAL) |
| return 0; |
| |
| parse_rtattr(rta, IFA_MAX, RTM_RTA(rtm), IFA_PAYLOAD(h)); |
| if (rta[RTA_DST] == NULL) |
| return 0; |
| |
| /* setup the socket address */ |
| memset(&addr, 0, sizeof(addr)); |
| addr.ss_family = rtm->rtm_family; |
| switch (rtm->rtm_family) { |
| case AF_INET: |
| sin = (struct sockaddr_in *) &addr; |
| memcpy(&sin->sin_addr, RTA_DATA(rta[RTA_DST]), |
| sizeof(sin->sin_addr)); |
| break; |
| #ifdef INET6 |
| case AF_INET6: |
| sin6 = (struct sockaddr_in6 *) &addr; |
| memcpy(&sin6->sin6_addr, RTA_DATA(rta[RTA_DST]), |
| sizeof(sin6->sin6_addr)); |
| /* Link-local addresses are handled with RTM_NEWADDR |
| * notifications */ |
| if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) |
| return 0; |
| break; |
| #endif |
| default: |
| return 0; |
| } |
| |
| /* If local route was deleted, check if there is still local |
| * route for the same IP on another interface */ |
| if (h->nlmsg_type == RTM_DELROUTE && |
| netlink_route_is_local(rtm->rtm_family, |
| RTA_DATA(rta[RTA_DST]), |
| RTA_PAYLOAD(rta[RTA_DST]))) { |
| plog(LLV_DEBUG, LOCATION, NULL, |
| "Netlink: not deleting %s yet, it exists still\n", |
| saddrwop2str((struct sockaddr *) &addr)); |
| return 0; |
| } |
| |
| netlink_add_del_address(h->nlmsg_type == RTM_NEWROUTE, |
| (struct sockaddr *) &addr); |
| return 0; |
| } |
| |
| static int |
| netlink_process(struct nlmsghdr *h) |
| { |
| switch (h->nlmsg_type) { |
| #ifdef INET6 |
| case RTM_NEWADDR: |
| case RTM_DELADDR: |
| return netlink_process_addr(h); |
| #endif |
| case RTM_NEWROUTE: |
| case RTM_DELROUTE: |
| return netlink_process_route(h); |
| } |
| return 0; |
| } |
| |
| static int |
| kernel_receive(ctx, fd) |
| void *ctx; |
| int fd; |
| { |
| struct sockaddr_nl nladdr; |
| struct iovec iov; |
| struct msghdr msg = { |
| .msg_name = &nladdr, |
| .msg_namelen = sizeof(nladdr), |
| .msg_iov = &iov, |
| .msg_iovlen = 1, |
| }; |
| struct nlmsghdr *h; |
| int len, status; |
| char buf[16*1024]; |
| |
| iov.iov_base = buf; |
| while (1) { |
| iov.iov_len = sizeof(buf); |
| status = recvmsg(fd, &msg, MSG_DONTWAIT); |
| if (status < 0) { |
| if (errno == EINTR) |
| continue; |
| if (errno == EAGAIN) |
| return FALSE; |
| continue; |
| } |
| if (status == 0) |
| return FALSE; |
| |
| h = (struct nlmsghdr *) buf; |
| while (NLMSG_OK(h, status)) { |
| netlink_process(h); |
| h = NLMSG_NEXT(h, status); |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static int |
| netlink_open_socket() |
| { |
| int fd; |
| |
| fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); |
| if (fd < 0) { |
| plog(LLV_ERROR, LOCATION, NULL, |
| "socket(PF_NETLINK) failed: %s", |
| strerror(errno)); |
| return -1; |
| } |
| close_on_exec(fd); |
| if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) |
| plog(LLV_WARNING, LOCATION, NULL, |
| "failed to put socket in non-blocking mode\n"); |
| |
| return fd; |
| } |
| |
| static int |
| kernel_open_socket() |
| { |
| struct sockaddr_nl nl; |
| int fd; |
| |
| if (netlink_fd < 0) { |
| netlink_fd = netlink_open_socket(); |
| if (netlink_fd < 0) |
| return -1; |
| } |
| |
| fd = netlink_open_socket(); |
| if (fd < 0) |
| return fd; |
| |
| /* We monitor IPv4 addresses using RTMGRP_IPV4_ROUTE group |
| * the get the RTN_LOCAL routes which are automatically added |
| * by kernel. This is because: |
| * - Linux kernel has a bug that calling bind() immediately |
| * after IPv4 RTM_NEWADDR event can fail |
| * - if IP is configured in multiple interfaces, we get |
| * RTM_DELADDR for each of them. RTN_LOCAL gets deleted only |
| * after the last IP address is deconfigured. |
| * The latter reason is also why I chose to use route |
| * notifications for IPv6. However, we do need to use RTM_NEWADDR |
| * for the link-local IPv6 addresses to get the interface index |
| * that is needed in bind(). |
| */ |
| memset(&nl, 0, sizeof(nl)); |
| nl.nl_family = AF_NETLINK; |
| nl.nl_groups = RTMGRP_IPV4_ROUTE |
| #ifdef INET6 |
| | RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE |
| #endif |
| ; |
| if (bind(fd, (struct sockaddr*) &nl, sizeof(nl)) < 0) { |
| plog(LLV_ERROR, LOCATION, NULL, |
| "bind(PF_NETLINK) failed: %s\n", |
| strerror(errno)); |
| close(fd); |
| return -1; |
| } |
| return fd; |
| } |
| |
| static void |
| kernel_sync() |
| { |
| int fd = lcconf->rtsock; |
| |
| /* refresh addresses */ |
| if (!netlink_enumerate(fd, PF_UNSPEC, RTM_GETROUTE)) { |
| plog(LLV_ERROR, LOCATION, NULL, |
| "unable to enumerate addresses: %s\n", |
| strerror(errno)); |
| } |
| while (kernel_receive(NULL, fd) == TRUE); |
| |
| #ifdef INET6 |
| if (!netlink_enumerate(fd, PF_INET6, RTM_GETADDR)) { |
| plog(LLV_ERROR, LOCATION, NULL, |
| "unable to enumerate addresses: %s\n", |
| strerror(errno)); |
| } |
| while (kernel_receive(NULL, fd) == TRUE); |
| #endif |
| } |
| |
| #elif defined(USE_ROUTE) |
| |
| #define ROUNDUP(a) \ |
| ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) |
| |
| #define SAROUNDUP(X) ROUNDUP(((struct sockaddr *)(X))->sa_len) |
| |
| static size_t |
| parse_address(start, end, dest) |
| caddr_t start; |
| caddr_t end; |
| struct sockaddr_storage *dest; |
| { |
| int len; |
| |
| if (start >= end) |
| return 0; |
| |
| len = SAROUNDUP(start); |
| if (start + len > end) |
| return end - start; |
| |
| if (dest != NULL && len <= sizeof(struct sockaddr_storage)) |
| memcpy(dest, start, len); |
| |
| return len; |
| } |
| |
| static void |
| parse_addresses(start, end, flags, addr) |
| caddr_t start; |
| caddr_t end; |
| int flags; |
| struct sockaddr_storage *addr; |
| { |
| memset(addr, 0, sizeof(*addr)); |
| if (flags & RTA_DST) |
| start += parse_address(start, end, NULL); |
| if (flags & RTA_GATEWAY) |
| start += parse_address(start, end, NULL); |
| if (flags & RTA_NETMASK) |
| start += parse_address(start, end, NULL); |
| if (flags & RTA_GENMASK) |
| start += parse_address(start, end, NULL); |
| if (flags & RTA_IFP) |
| start += parse_address(start, end, NULL); |
| if (flags & RTA_IFA) |
| start += parse_address(start, end, addr); |
| if (flags & RTA_AUTHOR) |
| start += parse_address(start, end, NULL); |
| if (flags & RTA_BRD) |
| start += parse_address(start, end, NULL); |
| } |
| |
| static void |
| kernel_handle_message(msg) |
| caddr_t msg; |
| { |
| struct rt_msghdr *rtm = (struct rt_msghdr *) msg; |
| struct ifa_msghdr *ifa = (struct ifa_msghdr *) msg; |
| struct sockaddr_storage addr; |
| |
| switch (rtm->rtm_type) { |
| case RTM_NEWADDR: |
| parse_addresses(ifa + 1, msg + ifa->ifam_msglen, |
| ifa->ifam_addrs, &addr); |
| myaddr_open_all_configured((struct sockaddr *) &addr); |
| break; |
| case RTM_DELADDR: |
| parse_addresses(ifa + 1, msg + ifa->ifam_msglen, |
| ifa->ifam_addrs, &addr); |
| myaddr_close_all_open((struct sockaddr *) &addr); |
| break; |
| case RTM_ADD: |
| case RTM_DELETE: |
| case RTM_CHANGE: |
| case RTM_MISS: |
| case RTM_IFINFO: |
| #ifdef RTM_OIFINFO |
| case RTM_OIFINFO: |
| #endif |
| #ifdef RTM_NEWMADDR |
| case RTM_NEWMADDR: |
| case RTM_DELMADDR: |
| #endif |
| #ifdef RTM_IFANNOUNCE |
| case RTM_IFANNOUNCE: |
| #endif |
| break; |
| default: |
| plog(LLV_WARNING, LOCATION, NULL, |
| "unrecognized route message with rtm_type: %d", |
| rtm->rtm_type); |
| break; |
| } |
| } |
| |
| static int |
| kernel_receive(ctx, fd) |
| void *ctx; |
| int fd; |
| { |
| char buf[16*1024]; |
| struct rt_msghdr *rtm = (struct rt_msghdr *) buf; |
| int len; |
| |
| len = read(fd, &buf, sizeof(buf)); |
| if (len <= 0) { |
| if (len < 0 && errno != EWOULDBLOCK && errno != EAGAIN) |
| plog(LLV_WARNING, LOCATION, NULL, |
| "routing socket error: %s", strerror(errno)); |
| return FALSE; |
| } |
| |
| if (rtm->rtm_msglen != len) { |
| plog(LLV_WARNING, LOCATION, NULL, |
| "kernel_receive: rtm->rtm_msglen %d, len %d, type %d\n", |
| rtm->rtm_msglen, len, rtm->rtm_type); |
| return FALSE; |
| } |
| |
| kernel_handle_message(buf); |
| return TRUE; |
| } |
| |
| static int |
| kernel_open_socket() |
| { |
| int fd; |
| |
| fd = socket(PF_ROUTE, SOCK_RAW, 0); |
| if (fd < 0) { |
| plog(LLV_ERROR, LOCATION, NULL, |
| "socket(PF_ROUTE) failed: %s", |
| strerror(errno)); |
| return -1; |
| } |
| close_on_exec(fd); |
| if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) |
| plog(LLV_WARNING, LOCATION, NULL, |
| "failed to put socket in non-blocking mode\n"); |
| |
| return fd; |
| } |
| |
| static void |
| kernel_sync() |
| { |
| caddr_t ref, buf, end; |
| size_t bufsiz; |
| struct if_msghdr *ifm; |
| struct interface *ifp; |
| |
| #define MIBSIZ 6 |
| int mib[MIBSIZ] = { |
| CTL_NET, |
| PF_ROUTE, |
| 0, |
| 0, /* AF_INET & AF_INET6 */ |
| NET_RT_IFLIST, |
| 0 |
| }; |
| |
| if (sysctl(mib, MIBSIZ, NULL, &bufsiz, NULL, 0) < 0) { |
| plog(LLV_WARNING, LOCATION, NULL, |
| "sysctl() error: %s", strerror(errno)); |
| return; |
| } |
| |
| ref = buf = racoon_malloc(bufsiz); |
| |
| if (sysctl(mib, MIBSIZ, buf, &bufsiz, NULL, 0) >= 0) { |
| /* Parse both interfaces and addresses. */ |
| for (end = buf + bufsiz; buf < end; buf += ifm->ifm_msglen) { |
| ifm = (struct if_msghdr *) buf; |
| kernel_handle_message(buf); |
| } |
| } else { |
| plog(LLV_WARNING, LOCATION, NULL, |
| "sysctl() error: %s", strerror(errno)); |
| } |
| |
| racoon_free(ref); |
| } |
| |
| #else |
| |
| #error No supported interface to monitor local addresses. |
| |
| #endif |