forked from jbagg/QtZeroConf
392 lines
13 KiB
C
392 lines
13 KiB
C
/***
|
|
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/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. */
|
|
}
|