| /*** |
| 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 <config.h> |
| #endif |
| |
| #include <string.h> |
| #include <net/if.h> |
| #include <errno.h> |
| #include <string.h> |
| |
| #include "avahi-common/avahi-malloc.h" |
| |
| #include "log.h" |
| #include "iface.h" |
| #include "iface-linux.h" |
| |
| #ifndef IFLA_RTA |
| #include <linux/if_addr.h> |
| #define IFLA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) |
| #endif |
| |
| #ifndef IFA_RTA |
| #include <linux/if_addr.h> |
| #define IFA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) |
| #endif |
| |
| static int netlink_list_items(AvahiNetlink *nl, uint16_t type, unsigned *ret_seq) { |
| struct nlmsghdr *n; |
| struct rtgenmsg *gen; |
| uint8_t req[1024]; |
| |
| /* Issue a wild dump NETLINK request */ |
| |
| memset(&req, 0, sizeof(req)); |
| n = (struct nlmsghdr*) req; |
| n->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)); |
| n->nlmsg_type = type; |
| n->nlmsg_flags = NLM_F_REQUEST|NLM_F_DUMP; |
| n->nlmsg_pid = 0; |
| |
| gen = NLMSG_DATA(n); |
| memset(gen, 0, sizeof(struct rtgenmsg)); |
| gen->rtgen_family = AF_UNSPEC; |
| |
| return avahi_netlink_send(nl, n, ret_seq); |
| } |
| |
| static void netlink_callback(AvahiNetlink *nl, struct nlmsghdr *n, void* userdata) { |
| AvahiInterfaceMonitor *m = userdata; |
| |
| /* This routine is called for every RTNETLINK response packet */ |
| |
| assert(m); |
| assert(n); |
| assert(m->osdep.netlink == nl); |
| |
| if (n->nlmsg_type == RTM_NEWLINK) { |
| |
| /* A new interface appeared or an existing one has been modified */ |
| |
| struct ifinfomsg *ifinfomsg = NLMSG_DATA(n); |
| AvahiHwInterface *hw; |
| struct rtattr *a = NULL; |
| size_t l; |
| |
| /* A (superfluous?) sanity check */ |
| if (ifinfomsg->ifi_family != AF_UNSPEC) |
| return; |
| |
| /* Check whether there already is an AvahiHwInterface object |
| * for this link, so that we can update its data. Note that |
| * Netlink sends us an RTM_NEWLINK not only when a new |
| * interface appears, but when it changes, too */ |
| |
| if (!(hw = avahi_interface_monitor_get_hw_interface(m, ifinfomsg->ifi_index))) |
| |
| /* No object found, so let's create a new |
| * one. avahi_hw_interface_new() will call |
| * avahi_interface_new() internally twice for IPv4 and |
| * IPv6, so there is no need for us to do that |
| * ourselves */ |
| if (!(hw = avahi_hw_interface_new(m, (AvahiIfIndex) ifinfomsg->ifi_index))) |
| return; /* OOM */ |
| |
| /* Check whether the flags of this interface are OK for us */ |
| hw->flags_ok = |
| (ifinfomsg->ifi_flags & IFF_UP) && |
| (!m->server->config.use_iff_running || (ifinfomsg->ifi_flags & IFF_RUNNING)) && |
| !(ifinfomsg->ifi_flags & IFF_LOOPBACK) && |
| (ifinfomsg->ifi_flags & IFF_MULTICAST) && |
| (m->server->config.allow_point_to_point || !(ifinfomsg->ifi_flags & IFF_POINTOPOINT)); |
| |
| /* Handle interface attributes */ |
| l = NLMSG_PAYLOAD(n, sizeof(struct ifinfomsg)); |
| a = IFLA_RTA(ifinfomsg); |
| |
| while (RTA_OK(a, l)) { |
| switch(a->rta_type) { |
| case IFLA_IFNAME: |
| |
| /* Fill in interface name */ |
| avahi_free(hw->name); |
| hw->name = avahi_strndup(RTA_DATA(a), RTA_PAYLOAD(a)); |
| break; |
| |
| case IFLA_MTU: |
| |
| /* Fill in MTU */ |
| assert(RTA_PAYLOAD(a) == sizeof(unsigned int)); |
| hw->mtu = *((unsigned int*) RTA_DATA(a)); |
| break; |
| |
| case IFLA_ADDRESS: |
| |
| /* Fill in hardware (MAC) address */ |
| hw->mac_address_size = RTA_PAYLOAD(a); |
| if (hw->mac_address_size > AVAHI_MAC_ADDRESS_MAX) |
| hw->mac_address_size = AVAHI_MAC_ADDRESS_MAX; |
| |
| memcpy(hw->mac_address, RTA_DATA(a), hw->mac_address_size); |
| break; |
| |
| default: |
| ; |
| } |
| |
| a = RTA_NEXT(a, l); |
| } |
| |
| /* Check whether this interface is now "relevant" for us. If |
| * it is Avahi will start to announce its records on this |
| * interface and send out queries for subscribed records on |
| * it */ |
| avahi_hw_interface_check_relevant(hw); |
| |
| /* Update any associated RRs of this interface. (i.e. the |
| * _workstation._tcp record containing the MAC address) */ |
| avahi_hw_interface_update_rrs(hw, 0); |
| |
| } else if (n->nlmsg_type == RTM_DELLINK) { |
| |
| /* An interface has been removed */ |
| |
| struct ifinfomsg *ifinfomsg = NLMSG_DATA(n); |
| AvahiHwInterface *hw; |
| |
| /* A (superfluous?) sanity check */ |
| if (ifinfomsg->ifi_family != AF_UNSPEC) |
| return; |
| |
| /* Get a reference to our AvahiHwInterface object of this interface */ |
| if (!(hw = avahi_interface_monitor_get_hw_interface(m, (AvahiIfIndex) ifinfomsg->ifi_index))) |
| return; |
| |
| /* Free our object */ |
| avahi_hw_interface_free(hw, 0); |
| |
| } else if (n->nlmsg_type == RTM_NEWADDR || n->nlmsg_type == RTM_DELADDR) { |
| |
| /* An address has been added, modified or removed */ |
| |
| struct ifaddrmsg *ifaddrmsg = NLMSG_DATA(n); |
| AvahiInterface *i; |
| struct rtattr *a = NULL; |
| size_t l; |
| AvahiAddress raddr, rlocal, *r; |
| int raddr_valid = 0, rlocal_valid = 0; |
| |
| /* We are only interested in IPv4 and IPv6 */ |
| if (ifaddrmsg->ifa_family != AF_INET && ifaddrmsg->ifa_family != AF_INET6) |
| return; |
| |
| /* Try to get a reference to our AvahiInterface object for the |
| * interface this address is assigned to. If ther is no object |
| * for this interface, we ignore this address. */ |
| if (!(i = avahi_interface_monitor_get_interface(m, (AvahiIfIndex) ifaddrmsg->ifa_index, avahi_af_to_proto(ifaddrmsg->ifa_family)))) |
| return; |
| |
| /* Fill in address family for our new address */ |
| rlocal.proto = raddr.proto = avahi_af_to_proto(ifaddrmsg->ifa_family); |
| |
| l = NLMSG_PAYLOAD(n, sizeof(struct ifaddrmsg)); |
| a = IFA_RTA(ifaddrmsg); |
| |
| while (RTA_OK(a, l)) { |
| |
| switch(a->rta_type) { |
| |
| case IFA_ADDRESS: |
| |
| if ((rlocal.proto == AVAHI_PROTO_INET6 && RTA_PAYLOAD(a) != 16) || |
| (rlocal.proto == AVAHI_PROTO_INET && RTA_PAYLOAD(a) != 4)) |
| return; |
| |
| memcpy(rlocal.data.data, RTA_DATA(a), RTA_PAYLOAD(a)); |
| rlocal_valid = 1; |
| |
| break; |
| |
| case IFA_LOCAL: |
| |
| /* Fill in local address data. Usually this is |
| * preferable over IFA_ADDRESS if both are set, |
| * since this refers to the local address of a PPP |
| * link while IFA_ADDRESS refers to the other |
| * end. */ |
| |
| if ((raddr.proto == AVAHI_PROTO_INET6 && RTA_PAYLOAD(a) != 16) || |
| (raddr.proto == AVAHI_PROTO_INET && RTA_PAYLOAD(a) != 4)) |
| return; |
| |
| memcpy(raddr.data.data, RTA_DATA(a), RTA_PAYLOAD(a)); |
| raddr_valid = 1; |
| |
| break; |
| |
| default: |
| ; |
| } |
| |
| a = RTA_NEXT(a, l); |
| } |
| |
| /* If there was no adress attached to this message, let's quit. */ |
| if (rlocal_valid) |
| r = &rlocal; |
| else if (raddr_valid) |
| r = &raddr; |
| else |
| return; |
| |
| if (n->nlmsg_type == RTM_NEWADDR) { |
| AvahiInterfaceAddress *addr; |
| |
| /* This address is new or has been modified, so let's get an object for it */ |
| if (!(addr = avahi_interface_monitor_get_address(m, i, r))) |
| |
| /* Mmm, no object existing yet, so let's create a new one */ |
| if (!(addr = avahi_interface_address_new(m, i, r, ifaddrmsg->ifa_prefixlen))) |
| return; /* OOM */ |
| |
| /* Update the scope field for the address */ |
| addr->global_scope = ifaddrmsg->ifa_scope == RT_SCOPE_UNIVERSE || ifaddrmsg->ifa_scope == RT_SCOPE_SITE; |
| addr->deprecated = !!(ifaddrmsg->ifa_flags & IFA_F_DEPRECATED); |
| } else { |
| AvahiInterfaceAddress *addr; |
| assert(n->nlmsg_type == RTM_DELADDR); |
| |
| /* Try to get a reference to our AvahiInterfaceAddress object for this address */ |
| if (!(addr = avahi_interface_monitor_get_address(m, i, r))) |
| return; |
| |
| /* And free it */ |
| avahi_interface_address_free(addr); |
| } |
| |
| /* Avahi only considers interfaces with at least one address |
| * attached relevant. Since we migh have added or removed an |
| * address, let's have it check again whether the interface is |
| * now relevant */ |
| avahi_interface_check_relevant(i); |
| |
| /* Update any associated RRs, like A or AAAA for our new/removed address */ |
| avahi_interface_update_rrs(i, 0); |
| |
| } else if (n->nlmsg_type == NLMSG_DONE) { |
| |
| /* This wild dump request ended, so let's see what we do next */ |
| |
| if (m->osdep.list == LIST_IFACE) { |
| |
| /* Mmmm, interfaces have been wild dumped already, so |
| * let's go on with wild dumping the addresses */ |
| |
| if (netlink_list_items(m->osdep.netlink, RTM_GETADDR, &m->osdep.query_addr_seq) < 0) { |
| avahi_log_warn("NETLINK: Failed to list addrs: %s", strerror(errno)); |
| m->osdep.list = LIST_DONE; |
| } else |
| |
| /* Update state information */ |
| m->osdep.list = LIST_ADDR; |
| |
| } else |
| /* We're done. Tell avahi_interface_monitor_sync() to finish. */ |
| m->osdep.list = LIST_DONE; |
| |
| if (m->osdep.list == LIST_DONE) { |
| |
| /* Only after this boolean variable has been set, Avahi |
| * will start to announce or browse on all interfaces. It |
| * is originaly set to 0, which means that relevancy |
| * checks and RR updates are disabled during the wild |
| * dumps. */ |
| m->list_complete = 1; |
| |
| /* So let's check if any interfaces are relevant now */ |
| avahi_interface_monitor_check_relevant(m); |
| |
| /* And update all RRs attached to any interface */ |
| avahi_interface_monitor_update_rrs(m, 0); |
| |
| /* Tell the user that the wild dump is complete */ |
| avahi_log_info("Network interface enumeration completed."); |
| } |
| |
| } else if (n->nlmsg_type == NLMSG_ERROR && |
| (n->nlmsg_seq == m->osdep.query_link_seq || n->nlmsg_seq == m->osdep.query_addr_seq)) { |
| struct nlmsgerr *e = NLMSG_DATA (n); |
| |
| /* Some kind of error happened. Let's just tell the user and |
| * ignore it otherwise */ |
| |
| if (e->error) |
| avahi_log_warn("NETLINK: Failed to browse: %s", strerror(-e->error)); |
| } |
| } |
| |
| int avahi_interface_monitor_init_osdep(AvahiInterfaceMonitor *m) { |
| assert(m); |
| |
| /* Initialize our own data */ |
| |
| m->osdep.netlink = NULL; |
| m->osdep.query_addr_seq = m->osdep.query_link_seq = 0; |
| |
| /* Create a netlink object for us. It abstracts some things and |
| * makes netlink easier to use. It will attach to the main loop |
| * for us and call netlink_callback() whenever an event |
| * happens. */ |
| if (!(m->osdep.netlink = avahi_netlink_new(m->server->poll_api, RTMGRP_LINK|RTMGRP_IPV4_IFADDR|RTMGRP_IPV6_IFADDR, netlink_callback, m))) |
| goto fail; |
| |
| /* Set the initial state. */ |
| m->osdep.list = LIST_IFACE; |
| |
| /* Start the wild dump for the interfaces */ |
| if (netlink_list_items(m->osdep.netlink, RTM_GETLINK, &m->osdep.query_link_seq) < 0) |
| goto fail; |
| |
| return 0; |
| |
| fail: |
| |
| if (m->osdep.netlink) { |
| avahi_netlink_free(m->osdep.netlink); |
| m->osdep.netlink = NULL; |
| } |
| |
| return -1; |
| } |
| |
| void avahi_interface_monitor_free_osdep(AvahiInterfaceMonitor *m) { |
| assert(m); |
| |
| if (m->osdep.netlink) { |
| avahi_netlink_free(m->osdep.netlink); |
| m->osdep.netlink = NULL; |
| } |
| } |
| |
| void avahi_interface_monitor_sync(AvahiInterfaceMonitor *m) { |
| assert(m); |
| |
| /* Let's handle netlink events until we are done with wild |
| * dumping */ |
| |
| while (!m->list_complete) |
| if (!avahi_netlink_work(m->osdep.netlink, 1) == 0) |
| break; |
| |
| /* At this point Avahi knows about all local interfaces and |
| * addresses in existance. */ |
| } |