Am 03.12.2013 12:11, schrieb Simon Kelley:

> The standard fix is to use --bind-dynamic, which can rely  on the
> existence of the extended API. Unfortunately that doesn't work on
> FreeBSD because it also relies on a a method of getting asynchronous
> events when interfaces/address come and go. Does FreeBSD have an
> equivalent of the Linux netlink socket?

Following up on this question, I asked the FreeBSD-hackers@ list and got
a useful pointer from Ian Lepore to the PF_ROUTE SOCK_RAW socket.
The family can be used as an address filter, I used AF_UNSPEC.

If a user-space application has such a route socket open, it can change
the route table, or receive information from the kernel - not only about
route changes, but also address changes, interface flag changes,
interfaces appearing or dematerializing.  You could use

The relevant information is in man 4 route, and in the headers
(sys/socket.h, net/if.h, for instance).

I dug around and have come up with a demo program that opens a route
socket, selects() for messages, receives them, and decodes those I would
find interesting for dnsmasq.  It took quite a bit of debugging to get
the addresses in the messages decoded properly.

It's not pretty in that it's mostly undocumented, contains some
arbitrarily sized buffers, but compiles and runs without warnings in the
strictest standard settings on FreeBSD 9.1 i386, 9.2 amd64 and 10-STABLE
amd64, and should not have blatant bugs (buffer overruns).

Sorry for not decoding the names of the message types (RTM_*) either, I
am out of energy for tonight ;)

The route socket exposes more information, but we probably don't care -
those message types are listed, but not decoded.


The demo program is at
<http://people.freebsd.org/%7Emandree/try-rtsock.c>, a copy is attached,
and to use it, fire up two logins.

In one, compile the program, f. i.:

   cc -O -Wall -o try-rtsock try-rtsock.c

Run it (does not require root permissions):

   ./try-rtsock


and in the other login, try around with adding aliases whilst watching
the output of try-rtsock.

(If two logins are too cumbersome, you can also use one, and run
"./try-rtsock &" in the background.)

ifconfig lo0 inet 127.0.1.1 alias       # add alias
ifconfig lo0 inet 127.0.1.1 -alias      # kill it

ifconfig em0 inet6 2001:.... -alias     # kill global address
ifconfig em0 inet6 accept_rtadv         # accept autoconf
rtsol -a                                # solicit autoconf
# ...

ifconfig gif0 plumb up                  # create new i'face
ifconfig gif0 down                      # mark it down
ifconfig gif0 unplumb                   # destroy it

and after each command see what you've got from try-rtsock.

Hope that helps.

Best regards
Matthias

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/select.h>

#include <net/if.h>
#include <net/route.h>

// only for decoding AF_LINK addresses:
#include <net/if_dl.h>

#include <netdb.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void barf(const char *pfx)
{
    perror(pfx);
    exit(EXIT_FAILURE);
}

union u {
    char buf[1024];
    struct if_msghdr ifm;
    struct ifa_msghdr ifam;
    struct if_announcemsghdr ifann;
};

static void dumpaddrs(const union u *u, size_t len, int addrs_mask, size_t of)
{
    size_t i;
    int maskvec[] = { RTA_DST, RTA_GATEWAY, RTA_NETMASK, RTA_GENMASK,
	RTA_IFP, RTA_IFA, RTA_AUTHOR, RTA_BRD };
    const char *maskvec_nam[] = { "RTA_DST", "RTA_GATEWAY", "RTA_NETMASK",
	"RTA_GENMASK", "RTA_IFP", "RTA_IFA", "RTA_AUTHOR", "RTA_BRD" };

    i = 0;
    while (of < len && i < sizeof(maskvec)/sizeof(maskvec[0])) {
	if (addrs_mask & maskvec[i]) {
	    const struct sockaddr *sa = (const struct sockaddr *)((const char *)u + of);
	    printf("  %s: ", maskvec_nam[i]);
	    char buf[1024];

	    size_t ll = sa->sa_len; /* getnameinfo barfs if this is too short */
	    if (ll < sizeof(struct sockaddr)) ll = sizeof(struct sockaddr);

	    int r = getnameinfo(sa, ll, buf, sizeof(buf), 0, 0, NI_NUMERICHOST);
	    if (0 == r) {
		printf("%s", buf);
	    } else {
		printf("!ERROR(%s)", gai_strerror(r));
	    }

	    /* obtain interface name/index, getnameinfo only grabs the
	     * address (MAC for ether interfaces) */
	    if (sa->sa_family == AF_LINK) {
		const struct sockaddr_dl *sdl = (const struct sockaddr_dl *)sa;
		printf(" iface %.*s index %hu", sdl->sdl_nlen, sdl->sdl_data, sdl->sdl_index);
	    }
	    printf("\n");
	    size_t diff = sa->sa_len;
	    if (!diff) diff = sizeof(long);
	    of += diff;
	    /* round up as needed */
	    if (diff & (sizeof(long) - 1)) {
		of += sizeof(long) - (diff & (sizeof(long) - 1));
	    }
	}
	i++;
    }
}

struct flags {
    int mask;
    const char *flagname;
};

static struct flags flaglist[] = {
    { IFF_UP, "UP" },
    { IFF_BROADCAST, "bcast_valid" },
    { IFF_LOOPBACK, "loopback" },
    { IFF_POINTOPOINT, "P2P" },
    { IFF_DRV_RUNNING, "running" },
    { IFF_NOARP, "noARP" },
    { IFF_PROMISC, "promisc" },
    { IFF_DYING, "dying" },
    { IFF_RENAMING, "renaming" } };

static void decode_flags(int flags) {
    for (size_t i = 0; i < sizeof(flaglist)/sizeof(flaglist[0]); i++) {
	if (flags & flaglist[i].mask) {
	    printf(" %s", flaglist[i].flagname);
	}
    }
}

int main(void)
{
    int sock = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC); /* AF_UNSPEC: all addr families */
    if (-1 == sock) barf("socket");

    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(sock, &fds);

    printf("Use SIGINT or SIGTERM to end this program.\nReceiving from route socket...\n");

    int r;
    while ((r = select(sock+1, &fds, 0, 0, 0)), 1) {
	if (r == -1) { perror("select error"); continue; }
	if (r > 0 && FD_ISSET(sock, &fds)) {
	    union u u;
	    u.ifm.ifm_msglen = 4;
	    int r1 = recv(sock, &u, sizeof(u), 0);
	    if (r1 == -1) { perror("recv error"); continue; }
	    if (r1 < 4 || r1 < u.ifm.ifm_msglen) {
		printf("SHORT READ (have %d want %hu), SKIPPING.\n", r1, u.ifm.ifm_msglen);
		continue;
	    }

	    printf("Received %d bytes. Version %d, Type %#x, Len %d.\n", r1, u.ifm.ifm_version, u.ifm.ifm_type, u.ifm.ifm_msglen);

	    if (u.ifm.ifm_version != RTM_VERSION) {
		printf("Unknown version. Skipping.\n");
		continue;
	    }

	    switch (u.ifm.ifm_type) {
	    case RTM_NEWADDR:
	    case RTM_DELADDR:
		printf(" ##%s## - Addrmask %#x, Flags %#x, Index %hu, Metric %d\n",
			u.ifm.ifm_type == RTM_NEWADDR ? " NEW ADDRESS " : " DELETE ADDR ",
			u.ifam.ifam_addrs,
			u.ifam.ifam_flags,
			u.ifam.ifam_index,
			u.ifam.ifam_metric);
		dumpaddrs(&u, r1, u.ifam.ifam_addrs, sizeof(struct ifa_msghdr));
		break;

	    case RTM_IFINFO:
		printf("  INFO - Addrmask %#x, Index %hu, Flags %#x:",
			u.ifm.ifm_addrs, u.ifm.ifm_index, u.ifm.ifm_flags);
		decode_flags(u.ifm.ifm_flags);
		printf("\n");
		dumpaddrs(&u, r1, u.ifm.ifm_addrs, sizeof(struct if_msghdr));
		break;

	    case RTM_IFANNOUNCE:
		printf("  ANNOUNCE iface %.*s index %hu",
			IFNAMSIZ, u.ifann.ifan_name, u.ifann.ifan_index);
		switch(u.ifann.ifan_what)
		{
		    case IFAN_ARRIVAL: printf(" ARRIVED"); break;
		    case IFAN_DEPARTURE: printf(" DEPARTED"); break;
		    default: printf("Unknown action %hu", u.ifann.ifan_what);
		}
		printf("\n");
		break;
	    }
	}
	printf("\n");
    }
    exit(EXIT_SUCCESS);
}
_______________________________________________
Dnsmasq-discuss mailing list
Dnsmasq-discuss@lists.thekelleys.org.uk
http://lists.thekelleys.org.uk/mailman/listinfo/dnsmasq-discuss

Reply via email to