Hello tech@,

I have been developing a new daemon for OpenBSD that fills in a gap in
the multicast protocol support for network edges. More specifically I'm
talking about a multicast proxy. I'm sending this e-mail to share the
daemon code and see if there is interest in such.

The mcast-proxy is a less featured multicast routing daemon that is
mostly used on equipments that face client networks (end users). It is
mainly used when you don't need a full multicast routing daemon (like
dvmrpd, mrouted or pim), but you want to use your networks resources
efficiently. This implementation has the following features:

* Support IPv4 (IGMPv1/v2) multicast proxy
* Support IPv6 (MLDv1) multicast proxy
* Privilege dropping (runs as user)
* chroot jailing

The development of this daemon brought improvements to the IPv6
multicast stack, like:

* Initial MP support
  Now IPv6 multicast routing code uses the art routing table to store
  the multicast routes. This also means you can see your multicast
  routes in route(8).
* Support multiple rdomains
  The interfaces mif (multicast interface) are now domain specific, so
  you can have mif ids duplicated on different rdomains.
* Fixed a few problems in MLD code that prevented some client/server
  functionality

Note: the daemon is not yet pledge()d as there is no support for
MRT(6)_* setsockopt() calls.

Note 2: IPv6 multicast proxy requires an OpenBSD -current, because of
the recent kernel changes and netstat(8).

---

To run multicast routing protocols in your machines you have to configure
the following settings:

* Allow multicast routing:
  # rcctl enable multicast

* (IPv4 only) allow IGMP packets.
  To allow IP options you have to configure your PF traffic pass rule to
  accept IP options. Example: change 'pass' to 'pass allow-opts'.

* Add a multicast route (if the default doesn't exist or is not correct)
  IPv4: route add 224/8 192.168.0.1
  IPv6: route add ff00::/8 fe80::fce1:baff:fed0:2001%vio1

* In case you are using the default route for multicast you might need
  to specify an alternate multicast source. By default mcast-proxy only
  accepts multicast traffic from the same network of your interface.

  Example:
        em0 has IPv6 address: 2001:db8::100, but the multicast traffic comes
        from 2001:db9::10.

  The mcast-proxy.conf:
  ...
  interface em0 {
    source 2001:db9::/64
    upstream
  }
  ...

  The same applies for IPv4.

---

How to build it:

* Save this e-mail (e.g. /tmp/mail)
* Create a new directory (e.g. mkdir /tmp/mcast-proxy)
* Apply the diff in this email
  (e.g. cd/tmp/mcast-proxy; patch -p0 -i /tmp/mail)
* Build it (e.g. cd /tmp/mcast-proxy; make obj; make)
* Run it (e.g. /tmp/mcast-proxy/obj/mcast-proxy)

Reading the man pages:
* The daemon man page:
  cd /tmp/mcast-proxy; mandoc mcast-proxy.8 | less
* The configuration man page:
  cd /tmp/mcast-proxy; mandoc mcast-proxy.conf.5 | less

---

The daemon code is split in the following file hierarchy:

* mcast-proxy.c: all IGMP/MLD related packet parsing
* mrt.c: the multicast routing table on userland
* kroute.c: all kernel interactions
* util.c: misc functions that did not fit the other files


Here is the daemon code:

diff --git Makefile Makefile
new file mode 100644
index 0000000..d99eaed
--- /dev/null
+++ Makefile
@@ -0,0 +1,14 @@
+# $OpenBSD:$
+
+SRCS            = mcast-proxy.c kroute.c log.c mrt.c parse.y util.c
+PROG            = mcast-proxy
+MAN                     = mcast-proxy.8 mcast-proxy.conf.5
+
+CFLAGS   += -I${.CURDIR}
+CFLAGS         += -Wall -Wextra -Wshadow
+CFLAGS         += -Wmissing-prototypes -Wmissing-declarations
+CFLAGS         += -Wstrict-prototypes -Wpointer-arith -Wsign-compare
+DPADD                   = ${LIBEVENT}
+LDADD                   = -levent
+
+.include <bsd.prog.mk>
diff --git kroute.c kroute.c
new file mode 100644
index 0000000..af32091
--- /dev/null
+++ kroute.c
@@ -0,0 +1,1251 @@
+/*     $OpenBSD:$      */
+
+/*
+ * Copyright (c) 2017 Rafael Zalamena <rzalam...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/socket.h>
+
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/ip_mroute.h>
+#include <netinet6/ip6_mroute.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mcast-proxy.h"
+
+#define MAX_RTSOCK_BUF (128 * 1024)
+
+int bad_addr_v4(struct in_addr);
+int bad_addr_v6(struct in6_addr *);
+int iacmp(struct intf_addr *, struct intf_addr *);
+
+int vif4_nextvidx(void);
+int vif6_nextvidx(void);
+
+void if_announce(struct if_announcemsghdr *);
+void if_update(unsigned short, int, struct if_data *,
+    struct sockaddr_dl *sdl);
+void if_newaddr(unsigned short, struct sockaddr *, struct sockaddr *);
+void if_deladdr(unsigned short, struct sockaddr *, struct sockaddr *);
+void get_rtaddrs(int, struct sockaddr *, struct sockaddr **);
+void rtmsg_process(const uint8_t *, size_t);
+
+struct in6_addr in6_allrouters = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT;
+
+int vindex;
+int vindex6;
+int rtsd_rcvbuf;
+
+void
+assert_mcastforward(void)
+{
+       int              mforward = 0;
+       size_t           mforwardlen = sizeof(mforward);
+       int              mib[4];
+
+       if (!ic.ic_ipv4)
+               goto skip_v4mforwarding;
+
+       mib[0] = CTL_NET;
+       mib[1] = PF_INET;
+       mib[2] = IPPROTO_IP;
+       mib[3] = IPCTL_MFORWARDING;
+       if (sysctl(mib, nitems(mib), &mforward, &mforwardlen, NULL, 0) == -1)
+               fatal("sysctl IPv4 IPCTL_MFORWARDING");
+
+       if (!mforward)
+               fatalx("%s: IPv4 multicast forwarding is disabled",
+                   __func__);
+
+ skip_v4mforwarding:
+       if (!ic.ic_ipv6)
+               return;
+
+       mib[0] = CTL_NET;
+       mib[1] = PF_INET6;
+       mib[2] = IPPROTO_IPV6;
+       mib[3] = IPV6CTL_MFORWARDING;
+       if (sysctl(mib, nitems(mib), &mforward, &mforwardlen, NULL, 0) == -1)
+               fatal("sysctl IPv6 IPCTL_MFORWARDING");
+
+       if (!mforward)
+               fatalx("%s: IPv6 multicast forwarding is disabled",
+                   __func__);
+}
+
+int
+open_igmp_socket(void)
+{
+       int              sd, v;
+       uint8_t          ttl = 1, loop = 0;
+
+       sd = socket(AF_INET, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_IGMP);
+       if (sd == -1) {
+               log_warn("%s: socket", __func__);
+               return -1;
+       }
+
+       /* Initialize the multicast routing socket. */
+       v = 1;
+       if (setsockopt(sd, IPPROTO_IP, MRT_INIT, &v, sizeof(v)) == -1) {
+               log_warn("%s: setsockopt MRT_INIT", __func__);
+               close(sd);
+               return -1;
+       }
+
+       /* Include IP header on packets. */
+       v = 1;
+       if (setsockopt(sd, IPPROTO_IP, IP_HDRINCL, &v, sizeof(v)) == -1) {
+               log_warn("%s: setsockopt IP_HDRINCL", __func__);
+               close(sd);
+               return -1;
+       }
+
+       /* Use TTL of 1 to send multicast packets. */
+       if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,
+           sizeof(ttl)) == -1) {
+               log_warn("%s: setsockopt IP_MULTICAST_TTL", __func__);
+               close(sd);
+               return -1;
+       }
+
+       /* Don't send multicast packets to loopback. */
+       if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop,
+           sizeof(loop)) == -1) {
+               log_warn("%s: setsockopt IP_MULTICAST_LOOP", __func__);
+               close(sd);
+               return -1;
+       }
+
+       return sd;
+}
+
+int
+close_igmp_socket(int sd)
+{
+       if (sd == -1)
+               return 0;
+
+       if (setsockopt(sd, IPPROTO_IP, MRT_DONE, NULL, 0) == -1) {
+               log_warn("%s: setsockopt MRT_DONE", __func__);
+               return -1;
+       }
+
+       if (close(sd) == -1) {
+               log_warn("%s: close", __func__);
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+open_mld_socket(void)
+{
+       int              sd, v;
+       unsigned int     ttl = 1;
+
+       sd = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMPV6);
+       if (sd == -1) {
+               log_warn("%s: socket", __func__);
+               return -1;
+       }
+
+       /* Initialize the multicast routing socket. */
+       v = 1;
+       if (setsockopt(sd, IPPROTO_IPV6, MRT6_INIT, &v, sizeof(v)) == -1) {
+               log_warn("%s: setsockopt MRT6_INIT", __func__);
+               close(sd);
+               return -1;
+       }
+
+       /* Include IP header on packets. */
+       v = 1;
+       if (setsockopt(sd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &v,
+           sizeof(v)) == -1) {
+               log_warn("%s: setsockopt IPV6_RECVPKTINFO", __func__);
+               close(sd);
+               return -1;
+       }
+
+       /* Use TTL of 1 to send multicast packets. */
+       if (setsockopt(sd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl,
+           sizeof(ttl)) == -1) {
+               log_warn("%s: setsockopt IPV6_MULTICAST_HOPS", __func__);
+               close(sd);
+               return -1;
+       }
+
+       return sd;
+}
+
+int
+close_mld_socket(int sd)
+{
+       if (sd == -1)
+               return 0;
+
+       if (setsockopt(sd, IPPROTO_IPV6, MRT6_DONE, NULL, 0) == -1) {
+               log_warn("%s: setsockopt MRT6_DONE", __func__);
+               return -1;
+       }
+
+       if (close(sd) == -1) {
+               log_warn("%s: close", __func__);
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+igmp_setif(struct intf_data *id)
+{
+       struct intf_addr        *ia;
+       struct in_addr           any;
+
+       if (id == NULL) {
+               memset(&any, 0, sizeof(any));
+               if (setsockopt(igmpsd, IPPROTO_IP, IP_MULTICAST_IF,
+                   &any, sizeof(any)) == -1) {
+                       log_warn("%s: setsockopt IP_MULTICAST_IF default",
+                           __func__);
+                       return -1;
+               }
+               return 0;
+       }
+
+       ia = intf_primaryv4(id);
+       if (ia == NULL)
+               return -1;
+
+       if (setsockopt(igmpsd, IPPROTO_IP, IP_MULTICAST_IF,
+           &ia->ia_addr.v4, sizeof(ia->ia_addr.v4)) == -1) {
+               log_warn("%s: setsockopt IP_MULTICAST_IF %s",
+                   __func__, id->id_name);
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+vif_register(struct intf_data *id)
+{
+       int     error = 0;
+
+       if (id->id_vindex == INVALID_VINDEX)
+               error |= vif4_register(id);
+       if (id->id_vindex6 == INVALID_VINDEX)
+               error |= vif6_register(id);
+
+       return error;
+}
+
+int
+vif_unregister(struct intf_data *id)
+{
+       int     error = 0;
+
+       if (id->id_vindex != INVALID_VINDEX)
+               error |= vif4_unregister(id);
+       if (id->id_vindex != INVALID_VINDEX)
+               error |= vif6_unregister(id);
+
+       return error;
+}
+
+int
+vif4_nextvidx(void)
+{
+       struct intf_data        *id;
+       int                      vidx;
+
+       for (vidx = 0; vidx < MAXMIFS; vidx++) {
+               SLIST_FOREACH(id, &iflist, id_entry) {
+                       if (vidx == id->id_vindex)
+                               break;
+               }
+               if (id != NULL)
+                       continue;
+
+               return vidx;
+       }
+
+       return -1;
+}
+
+int
+vif4_register(struct intf_data *id)
+{
+       struct intf_addr        *ia;
+       struct vifctl            vifc;
+       int                      vidx;
+
+       /* Don't allow registration if not selected. */
+       if (!id->id_mv4)
+               return 0;
+
+       /* Already registered. */
+       if (id->id_vindex != INVALID_VINDEX)
+               return 0;
+
+       ia = intf_primaryv4(id);
+       if (ia == NULL)
+               return -1;
+
+       memset(&vifc, 0, sizeof(vifc));
+       vifc.vifc_flags = 0;
+       vifc.vifc_threshold = id->id_ttl;
+       vifc.vifc_rate_limit = 0;
+       vifc.vifc_lcl_addr = ia->ia_addr.v4;
+       vifc.vifc_rmt_addr.s_addr = INADDR_ANY;
+
+       vidx = vif4_nextvidx();
+       if (vidx == -1) {
+               log_warnx("%s: no more virtual interfaces available",
+                   __func__);
+               return -1;
+       }
+
+       vifc.vifc_vifi = id->id_vindex = vidx;
+       log_debug("%s: %s (vindex %d) threshold %d rate %d address %s",
+           __func__, id->id_name, id->id_vindex, id->id_ttl, 0,
+           addr4tostr(&ia->ia_addr.v4));
+
+       if (setsockopt(igmpsd, IPPROTO_IP, MRT_ADD_VIF, &vifc,
+           sizeof(vifc)) == -1) {
+               id->id_vindex = INVALID_VINDEX;
+               log_warn("%s: setsockopt MRT_ADD_VIF", __func__);
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+vif4_unregister(struct intf_data *id)
+{
+       struct intf_addr        *ia;
+       struct vifctl            vifc;
+
+       /* Don't allow registration if not selected. */
+       if (!id->id_mv4)
+               return 0;
+
+       /* Already unregistered. */
+       if (id->id_vindex == INVALID_VINDEX)
+               return 0;
+
+       ia = intf_primaryv4(id);
+       if (ia == NULL)
+               return -1;
+
+       memset(&vifc, 0, sizeof(vifc));
+       vifc.vifc_flags = 0;
+       vifc.vifc_vifi = id->id_vindex;
+       vifc.vifc_threshold = id->id_ttl;
+       vifc.vifc_rate_limit = 0;
+       vifc.vifc_lcl_addr = ia->ia_addr.v4;
+       vifc.vifc_rmt_addr.s_addr = INADDR_ANY;
+
+       log_debug("%s: %s (%d) threshold %d rate %d address %s",
+           __func__, id->id_name, id->id_vindex, id->id_ttl, 0,
+           addr4tostr(&ia->ia_addr.v4));
+
+       if (setsockopt(igmpsd, IPPROTO_IP, MRT_DEL_VIF, &vifc,
+           sizeof(vifc)) == -1) {
+               log_warn("%s: setsockopt MRT_DEL_VIF", __func__);
+               return -1;
+       }
+
+       id->id_vindex = INVALID_VINDEX;
+
+       return 0;
+}
+
+int
+vif6_nextvidx(void)
+{
+       struct intf_data        *id;
+       int                      vidx;
+
+       for (vidx = 0; vidx < MAXMIFS; vidx++) {
+               SLIST_FOREACH(id, &iflist, id_entry) {
+                       if (vidx == id->id_vindex6)
+                               break;
+               }
+               if (id != NULL)
+                       continue;
+
+               return vidx;
+       }
+
+       return -1;
+}
+
+int
+vif6_register(struct intf_data *id)
+{
+       struct mif6ctl           mif6c;
+       int                      vidx;
+
+       /* Don't allow registration if not selected. */
+       if (!id->id_mv6)
+               return 0;
+
+       /* Already registered. */
+       if (id->id_vindex6 != INVALID_VINDEX)
+               return 0;
+
+       memset(&mif6c, 0, sizeof(mif6c));
+       mif6c.mif6c_pifi = id->id_index;
+
+       vidx = vif6_nextvidx();
+       if (vidx == -1) {
+               log_warnx("%s: no more virtual interfaces available",
+                   __func__);
+               return -1;
+       }
+
+       id->id_vindex6 = mif6c.mif6c_mifi = vidx;
+       log_debug("%s: %s (vindex %d) rate %d",
+           __func__, id->id_name, id->id_vindex6, 0);
+
+       if (setsockopt(mldsd, IPPROTO_IPV6, MRT6_ADD_MIF, &mif6c,
+           sizeof(mif6c)) == -1) {
+               id->id_vindex6 = INVALID_VINDEX;
+               log_warn("%s: setsockopt MRT6_ADD_MIF", __func__);
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+vif6_unregister(struct intf_data *id)
+{
+       struct mif6ctl           mif6c;
+
+       /* Don't allow registration if not selected. */
+       if (!id->id_mv6)
+               return 0;
+
+       /* Already unregistered. */
+       if (id->id_vindex6 == INVALID_VINDEX)
+               return 0;
+
+       memset(&mif6c, 0, sizeof(mif6c));
+       mif6c.mif6c_pifi = id->id_index;
+
+       log_debug("%s: %s (vindex %d) rate %d",
+           __func__, id->id_name, id->id_vindex6, 0);
+
+       if (setsockopt(mldsd, IPPROTO_IPV6, MRT6_DEL_MIF, &mif6c,
+           sizeof(mif6c)) == -1) {
+               log_warn("%s: setsockopt MRT6_DEL_MIF", __func__);
+               return -1;
+       }
+
+       id->id_vindex6 = INVALID_VINDEX;
+
+       return 0;
+}
+
+int
+mcast_join(struct intf_data *id, struct sockaddr_storage *ss)
+{
+       int     error = 0;
+
+       if (ss == NULL) {
+               error |= mcast4_join(id, NULL);
+               error |= mcast6_join(id, NULL);
+       } else {
+               switch (ss->ss_family) {
+               case AF_INET:
+                       error = mcast4_join(id, &sstosin(ss)->sin_addr);
+                       break;
+               case AF_INET6:
+                       error = mcast6_join(id, &sstosin6(ss)->sin6_addr);
+                       break;
+
+               default:
+                       log_debug("%s: invalid protocol %d",
+                           __func__, ss->ss_family);
+                       error = -1;
+               }
+       }
+
+       return error;
+}
+
+int
+mcast_leave(struct intf_data *id, struct sockaddr_storage *ss)
+{
+       int     error = 0;
+
+       if (ss == NULL) {
+               error |= mcast4_leave(id, NULL);
+               error |= mcast6_leave(id, NULL);
+       } else {
+               switch (ss->ss_family) {
+               case AF_INET:
+                       error = mcast4_leave(id, &sstosin(ss)->sin_addr);
+                       break;
+               case AF_INET6:
+                       error = mcast6_leave(id, &sstosin6(ss)->sin6_addr);
+                       break;
+
+               default:
+                       log_debug("%s: invalid protocol %d",
+                           __func__, ss->ss_family);
+                       error = -1;
+               }
+       }
+
+       return error;
+}
+
+int
+mcast4_join(struct intf_data *id, struct in_addr *in)
+{
+       struct intf_addr        *ia;
+       struct ip_mreq           imr;
+
+       /* IPv4 is disabled in this interface. */
+       if (!id->id_mv4)
+               return 0;
+
+       ia = intf_primaryv4(id);
+       if (ia == NULL)
+               return -1;
+
+       if (in == NULL)
+               log_debug("%s: %s (%d) address %s group all_routers",
+                   __func__, id->id_name, id->id_vindex,
+                   addr4tostr(&ia->ia_addr.v4));
+       else
+               log_debug("%s: %s (%d) address %s group %s",
+                   __func__, id->id_name, id->id_vindex,
+                   addr4tostr(&ia->ia_addr.v4), addr4tostr(in));
+
+       imr.imr_multiaddr.s_addr = (in == NULL) ?
+           htonl(INADDR_ALLROUTERS_GROUP) : in->s_addr;
+       imr.imr_interface = ia->ia_addr.v4;
+       if (setsockopt(igmpsd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
+           sizeof(imr)) == -1) {
+               log_debug("%s: setsockopt IP_ADD_MEMBERSHIP: %s",
+                   __func__, strerror(errno));
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+mcast4_leave(struct intf_data *id, struct in_addr *in)
+{
+       struct intf_addr        *ia;
+       struct ip_mreq           imr;
+
+       /* IPv4 is disabled in this interface. */
+       if (!id->id_mv4)
+               return 0;
+
+       ia = intf_primaryv4(id);
+       if (ia == NULL)
+               return -1;
+
+       if (in == NULL)
+               log_debug("%s: %s (%d) address %s group all_routers",
+                   __func__, id->id_name, id->id_vindex,
+                   addr4tostr(&ia->ia_addr.v4));
+       else
+               log_debug("%s: %s (%d) address %s group %s",
+                   __func__, id->id_name, id->id_vindex,
+                   addr4tostr(&ia->ia_addr.v4), addr4tostr(in));
+
+       imr.imr_multiaddr.s_addr = (in == NULL) ?
+           htonl(INADDR_ALLROUTERS_GROUP) : in->s_addr;
+       imr.imr_interface = ia->ia_addr.v4;
+       if (setsockopt(igmpsd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr,
+           sizeof(imr)) == -1) {
+               log_debug("%s: setsockopt IP_DROP_MEMBERSHIP: %s",
+                   __func__, strerror(errno));
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+mcast6_join(struct intf_data *id, struct in6_addr *in6)
+{
+       struct ipv6_mreq         ipv6mr;
+
+       /* IPv6 is disabled in this interface. */
+       if (!id->id_mv6)
+               return 0;
+
+       if (in6 == NULL)
+               log_debug("%s: %s (%d) group all_routers",
+                   __func__, id->id_name, id->id_vindex6);
+       else
+               log_debug("%s: %s (%d) group %s",
+                   __func__, id->id_name, id->id_vindex6, addr6tostr(in6));
+
+       ipv6mr.ipv6mr_multiaddr = (in6 == NULL) ? in6_allrouters : *in6;
+       ipv6mr.ipv6mr_interface = id->id_index;
+       if (setsockopt(mldsd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) == -1) {
+               log_debug("%s: setsockopt IPV6_JOIN_GROUP: %s",
+                   __func__, strerror(errno));
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+mcast6_leave(struct intf_data *id, struct in6_addr *in6)
+{
+       struct ipv6_mreq         ipv6mr;
+
+       /* IPv6 is disabled in this interface. */
+       if (!id->id_mv6)
+               return 0;
+
+       if (in6 == NULL)
+               log_debug("%s: %s (%d) group all_routers",
+                   __func__, id->id_name, id->id_vindex6);
+       else
+               log_debug("%s: %s (%d) group %s",
+                   __func__, id->id_name, id->id_vindex6, addr6tostr(in6));
+
+       ipv6mr.ipv6mr_multiaddr = (in6 == NULL) ? in6_allrouters : *in6;
+       ipv6mr.ipv6mr_interface = id->id_index;
+       if (setsockopt(mldsd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &ipv6mr,
+           sizeof(ipv6mr)) == -1) {
+               log_warn("%s: setsockopt IPV6_LEAVE_GROUP: %s",
+                   __func__, strerror(errno));
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+mcast_addroute(unsigned short pvidx, union uaddr *origin,
+    union uaddr *group, struct molist *molist)
+{
+       struct intf_data        *id;
+       struct multicast_origin *mo;
+       struct mfcctl            mfcc;
+       unsigned short           vidx;
+
+       memset(&mfcc, 0, sizeof(mfcc));
+       mfcc.mfcc_origin = origin->v4;
+       mfcc.mfcc_mcastgrp = group->v4;
+       mfcc.mfcc_parent = pvidx;
+       LIST_FOREACH(mo, molist, mo_entry) {
+               id = mo->mo_id;
+
+               /* Don't set upstream interface TTL. */
+               if (id == upstreamif)
+                       continue;
+
+               vidx = id->id_vindex;
+               if (vidx > MAXVIFS)
+                       continue;
+
+               mfcc.mfcc_ttls[vidx] = id->id_ttl;
+       }
+
+       log_debug("%s: add route origin %s group %s parent %d",
+           __func__, addr4tostr(&origin->v4), addr4tostr(&group->v4),
+           pvidx);
+
+       LIST_FOREACH(mo, molist, mo_entry) {
+               id = mo->mo_id;
+               vidx = id->id_vindex;
+               if (vidx > MAXVIFS)
+                       continue;
+
+               if (mfcc.mfcc_ttls[vidx])
+                       log_debug("  vif %s (%d) ttl %d",
+                           id->id_name, vidx, mfcc.mfcc_ttls[vidx]);
+               else
+                       log_debug("  vif %s (%d) disabled",
+                           id->id_name, vidx);
+       }
+
+
+       if (setsockopt(igmpsd, IPPROTO_IP, MRT_ADD_MFC, &mfcc,
+           sizeof(mfcc)) == -1) {
+               log_warn("%s: setsockopt MRT_ADD_MFC", __func__);
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+mcast_addroute6(unsigned short pvidx, union uaddr *origin,
+    union uaddr *group, struct molist *molist)
+{
+       struct intf_data        *id;
+       struct multicast_origin *mo;
+       struct mf6cctl           mf6cc;
+       unsigned short           vidx;
+
+       memset(&mf6cc, 0, sizeof(mf6cc));
+       mf6cc.mf6cc_parent = pvidx;
+       mf6cc.mf6cc_origin.sin6_family = AF_INET6;
+       mf6cc.mf6cc_origin.sin6_addr = origin->v6;
+       mf6cc.mf6cc_origin.sin6_len = sizeof(mf6cc.mf6cc_origin);
+       mf6cc.mf6cc_mcastgrp.sin6_family = AF_INET6;
+       mf6cc.mf6cc_mcastgrp.sin6_addr = group->v6;
+       mf6cc.mf6cc_mcastgrp.sin6_len = sizeof(mf6cc.mf6cc_mcastgrp);
+       LIST_FOREACH(mo, molist, mo_entry) {
+               id = mo->mo_id;
+
+               /* Don't set upstream interface. */
+               if (id == upstreamif)
+                       continue;
+
+               vidx = id->id_vindex6;
+               if (vidx > MAXMIFS)
+                       continue;
+
+               IF_SET(vidx, &mf6cc.mf6cc_ifset);
+       }
+
+       log_debug("%s: add route origin %s group %s parent %d",
+           __func__, addr6tostr(&origin->v6), addr6tostr(&group->v6),
+           pvidx);
+
+       LIST_FOREACH(mo, molist, mo_entry) {
+               id = mo->mo_id;
+               vidx = id->id_vindex6;
+               if (vidx > MAXMIFS)
+                       continue;
+
+               if (IF_ISSET(vidx, &mf6cc.mf6cc_ifset))
+                       log_debug("  mif %s (%d)",
+                           id->id_name, vidx);
+               else
+                       log_debug("  mif %s (%d) disabled",
+                           id->id_name, vidx);
+       }
+
+
+       if (setsockopt(mldsd, IPPROTO_IPV6, MRT6_ADD_MFC, &mf6cc,
+           sizeof(mf6cc)) == -1) {
+               log_warn("%s: setsockopt MRT6_ADD_MFC", __func__);
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+mcast_delroute(unsigned short pvidx, union uaddr *origin,
+    union uaddr *group)
+{
+       struct mfcctl            mfcc;
+
+       memset(&mfcc, 0, sizeof(mfcc));
+       mfcc.mfcc_origin = origin->v4;
+       mfcc.mfcc_mcastgrp = group->v4;
+       mfcc.mfcc_parent = pvidx;
+
+       log_debug("%s: del route origin %s group %s parent %d",
+           __func__, addr4tostr(&origin->v4), addr4tostr(&group->v4),
+           pvidx);
+
+       if (setsockopt(igmpsd, IPPROTO_IP, MRT_DEL_MFC, &mfcc,
+           sizeof(mfcc)) == -1) {
+               log_warn("%s: setsockopt MRT_DEL_MFC", __func__);
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+mcast_delroute6(unsigned short pvidx, union uaddr *origin,
+    union uaddr *group)
+{
+       struct mf6cctl           mf6cc;
+
+       memset(&mf6cc, 0, sizeof(mf6cc));
+       mf6cc.mf6cc_parent = pvidx;
+       mf6cc.mf6cc_origin.sin6_addr = origin->v6;
+       mf6cc.mf6cc_mcastgrp.sin6_addr = group->v6;
+
+       log_debug("%s: del route origin %s group %s parent %d",
+           __func__, addr6tostr(&origin->v6), addr6tostr(&group->v6),
+           pvidx);
+
+       if (setsockopt(mldsd, IPPROTO_IPV6, MRT6_DEL_MFC, &mf6cc,
+           sizeof(mf6cc)) == -1) {
+               log_warn("%s: setsockopt MRT_DEL6_MFC", __func__);
+               return -1;
+       }
+
+       return 0;
+}
+
+void
+intf_dispatch(int sd, __unused short ev, __unused void *arg)
+{
+       ssize_t          n;
+       static uint8_t  *buf;
+
+       if (buf == NULL) {
+               buf = malloc(rtsd_rcvbuf);
+               if (buf == NULL)
+                       fatal("%s: malloc", __func__);
+       }
+
+       n = read(sd, buf, rtsd_rcvbuf);
+       if (n == -1) {
+               if (errno == EAGAIN || errno == EWOULDBLOCK ||
+                   errno == EINTR)
+                       return;
+
+               log_warn("%s: read", __func__);
+               return;
+       }
+       if (n == 0)
+               fatalx("%s: routing socket closed", __func__);
+
+       rtmsg_process(buf, n);
+}
+
+int
+intf_init(void)
+{
+       size_t           len;
+       int              mib[6];
+       uint8_t         *buf;
+       int              sd, opt, rcvbuf, defrcvbuf;
+       socklen_t        optlen;
+
+       mib[0] = CTL_NET;
+       mib[1] = PF_ROUTE;
+       mib[2] = 0;
+       mib[3] = 0;     /* wildcard */
+       mib[4] = NET_RT_IFLIST;
+       mib[5] = 0;
+
+       if (sysctl(mib, 6, NULL, &len, NULL, 0) == -1)
+               fatal("%s: sysctl", __func__);
+       if ((buf = malloc(len)) == NULL)
+               fatal("%s: malloc", __func__);
+       if (sysctl(mib, 6, buf, &len, NULL, 0) == -1) {
+               free(buf);
+               fatal("%s: sysctl", __func__);
+       }
+
+       rtmsg_process(buf, len);
+       free(buf);
+
+       sd = socket(AF_ROUTE, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+       if (sd == -1)
+               fatal("%s: socket", __func__);
+
+       opt = 0;
+       if (setsockopt(sd, SOL_SOCKET, SO_USELOOPBACK,
+           &opt, sizeof(opt)) == -1)
+               fatal("%s: setsockopt SO_USELOOPBACK", __func__);
+
+       /* Increase the receive buffer. */
+       rcvbuf = MAX_RTSOCK_BUF;
+       optlen = sizeof(rcvbuf);
+       if (getsockopt(sd, SOL_SOCKET, SO_RCVBUF,
+           &defrcvbuf, &optlen) == -1)
+               log_warn("%s: getsockopt SO_RCVBUF", __func__);
+       else
+               for (; rcvbuf > defrcvbuf &&
+                   setsockopt(sd, SOL_SOCKET, SO_RCVBUF,
+                   &rcvbuf, sizeof(rcvbuf)) == -1 && errno == ENOBUFS;
+                   rcvbuf /= 2)
+                       continue;
+
+       rtsd_rcvbuf = rcvbuf;
+
+       return (sd);
+}
+
+void
+if_announce(struct if_announcemsghdr *ifan)
+{
+       struct intf_data        *id;
+
+       if (ifan->ifan_what == IFAN_DEPARTURE) {
+               log_debug("%s departure: %s", __func__, ifan->ifan_name);
+
+               id = intf_lookupbyname(ifan->ifan_name);
+               if (id == NULL)
+                       return;
+
+               id->id_enabled = 0;
+               id->id_vindex = INVALID_VINDEX;
+               id->id_vindex6 = INVALID_VINDEX;
+               return;
+       } else
+               log_debug("%s arrival: %s", __func__, ifan->ifan_name);
+
+       id = intf_lookupbyname(ifan->ifan_name);
+       if (id == NULL) {
+               id = id_insert(ifan->ifan_index);
+               if (id == NULL)
+                       return;
+       }
+
+       id->id_index = ifan->ifan_index;
+       strlcpy(id->id_name, ifan->ifan_name, sizeof(id->id_name));
+}
+
+void
+if_update(unsigned short ifindex, int flags, struct if_data *ifd,
+    struct sockaddr_dl *sdl)
+{
+       struct intf_data        *id;
+       size_t                   sdllen = 0;
+       char                     ifname[IFNAMSIZ];
+
+       /* Don't install loopback interfaces. */
+       if ((flags & IFF_LOOPBACK) == IFF_LOOPBACK)
+               return;
+       /* Don't install non multicast interfaces. */
+       if ((flags & IFF_MULTICAST) != IFF_MULTICAST)
+               return;
+
+       /* Check for sdl and copy interface name. */
+       if (sdl == NULL || sdl->sdl_family != AF_LINK)
+               goto insert_interface;
+
+       sdllen = (sdl->sdl_nlen >= sizeof(id->id_name)) ?
+           (sizeof(id->id_name) - 1) : sdl->sdl_nlen;
+
+       memcpy(ifname, sdl->sdl_data, sdllen);
+       ifname[sdllen] = 0;
+
+       log_debug("%s: if %s (%d)", __func__, ifname, ifindex);
+
+       id = intf_lookupbyname(ifname);
+       if (id == NULL) {
+ insert_interface:
+               id = id_insert(ifindex);
+               if (id == NULL)
+                       return;
+       }
+
+       id->id_enabled = (flags & IFF_UP) &&
+           LINK_STATE_IS_UP(ifd->ifi_link_state);
+       id->id_index = ifindex;
+       id->id_flags = flags;
+       id->id_rdomain = ifd->ifi_rdomain;
+       if (sdllen > 0)
+               strlcpy(id->id_name, ifname, sizeof(id->id_name));
+}
+
+int
+bad_addr_v4(struct in_addr addr)
+{
+       uint32_t         a = ntohl(addr.s_addr);
+
+       if (((a >> IN_CLASSA_NSHIFT) == 0) ||
+           ((a >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) ||
+           IN_MULTICAST(a) || IN_BADCLASS(a))
+               return (1);
+
+       return (0);
+}
+
+int
+bad_addr_v6(struct in6_addr *addr)
+{
+       if (IN6_IS_ADDR_UNSPECIFIED(addr) ||
+           IN6_IS_ADDR_LOOPBACK(addr) ||
+           IN6_IS_ADDR_MULTICAST(addr) ||
+           IN6_IS_ADDR_SITELOCAL(addr) ||
+           IN6_IS_ADDR_V4MAPPED(addr) ||
+           IN6_IS_ADDR_V4COMPAT(addr))
+               return (1);
+
+       return (0);
+}
+
+void
+if_newaddr(unsigned short ifindex, struct sockaddr *ifa, struct sockaddr *mask)
+{
+       struct intf_data        *id;
+       struct intf_addr        *ia;
+       struct sockaddr_in      *ifa4, *mask4;
+       struct sockaddr_in6     *ifa6, *mask6;
+       int                      newaddr;
+
+       if (ifa == NULL)
+               return;
+
+       id = intf_lookupbyindex(ifindex);
+       if (id == NULL) {
+               log_debug("%s: corresponding if %d not found",
+                   __func__, ifindex);
+               return;
+       }
+
+       switch (ifa->sa_family) {
+       case AF_INET:
+               ifa4 = (struct sockaddr_in *) ifa;
+               mask4 = (struct sockaddr_in *) mask;
+
+               /* filter out unwanted addresses */
+               if (bad_addr_v4(ifa4->sin_addr))
+                       return;
+
+               ia = calloc(1, sizeof(*ia));
+               if (ia == NULL)
+                       fatal("%s: calloc", __func__);
+
+               ia->ia_addr.v4 = ifa4->sin_addr;
+               if (mask4)
+                       ia->ia_prefixlen =
+                           mask2prefixlen(mask4->sin_addr.s_addr);
+
+               log_debug("%s: if %s (%d): %s (prefixlen %d)",
+                   __func__, id->id_name, id->id_index,
+                   addr4tostr(&ifa4->sin_addr), ia->ia_prefixlen);
+               break;
+       case AF_INET6:
+               ifa6 = (struct sockaddr_in6 *) ifa;
+               mask6 = (struct sockaddr_in6 *) mask;
+
+               /* We only care about link-local and global-scope. */
+               if (bad_addr_v6(&ifa6->sin6_addr))
+                       return;
+
+               ia = calloc(1, sizeof(*ia));
+               if (ia == NULL)
+                       fatal("%s: calloc", __func__);
+
+               ia->ia_addr.v6 = ifa6->sin6_addr;
+               if (mask6)
+                       ia->ia_prefixlen = mask2prefixlen6(mask6);
+
+               log_debug("%s: if %s (%d): %s (prefixlen %d)",
+                   __func__, id->id_name, id->id_index,
+                   addr6tostr(&ifa6->sin6_addr), ia->ia_prefixlen);
+               break;
+       default:
+               return;
+       }
+
+       newaddr = (intf_primaryv4(id) == NULL);
+
+       ia->ia_af = ifa->sa_family;
+       ia_inserttail(&id->id_ialist, ia);
+
+       /*
+        * Register interface if it is a new primary address in a
+        * enabled interface.
+        */
+       if (newaddr && id->id_dir != IDIR_DISABLE) {
+               vif_register(id);
+               if (id->id_dir == IDIR_DOWNSTREAM)
+                       mcast_join(id, NULL);
+       }
+}
+
+int
+iacmp(struct intf_addr *ia, struct intf_addr *ian)
+{
+       if (ia->ia_af > ian->ia_af)
+               return -1;
+
+       return memcmp(&ia->ia_addr, &ian->ia_addr, (ia->ia_af == AF_INET) ?
+           sizeof(ia->ia_addr.v4) : sizeof(ia->ia_addr.v6));
+}
+
+void
+if_deladdr(unsigned short ifindex, struct sockaddr *ifa, struct sockaddr *mask)
+{
+       struct intf_data        *id;
+       struct intf_addr         iac, *ia;
+       struct sockaddr_in      *ifa4, *mask4;
+       struct sockaddr_in6     *ifa6, *mask6;
+       int                      regagain = 0;
+
+       if (ifa == NULL)
+               return;
+
+       id = intf_lookupbyindex(ifindex);
+       if (id == NULL) {
+               log_debug("%s: corresponding if %d not found",
+                   __func__, ifindex);
+               return;
+       }
+
+       memset(&iac, 0, sizeof(iac));
+       iac.ia_af = ifa->sa_family;
+       switch (ifa->sa_family) {
+       case AF_INET:
+               ifa4 = (struct sockaddr_in *) ifa;
+               mask4 = (struct sockaddr_in *) mask;
+
+               /* filter out unwanted addresses */
+               if (bad_addr_v4(ifa4->sin_addr))
+                       return;
+
+               iac.ia_addr.v4 = ifa4->sin_addr;
+               if (mask4)
+                       iac.ia_prefixlen =
+                           mask2prefixlen(mask4->sin_addr.s_addr);
+
+               log_debug("%s: if %s (%d): %s (prefixlen %d)",
+                   __func__, id->id_name, id->id_index,
+                   addr4tostr(&ifa4->sin_addr), iac.ia_prefixlen);
+               break;
+       case AF_INET6:
+               ifa6 = (struct sockaddr_in6 *) ifa;
+               mask6 = (struct sockaddr_in6 *) mask;
+
+               /* We only care about link-local and global-scope. */
+               if (bad_addr_v6(&ifa6->sin6_addr))
+                       return;
+
+               iac.ia_addr.v6 = ifa6->sin6_addr;
+               if (mask6)
+                       iac.ia_prefixlen = mask2prefixlen6(mask6);
+
+               log_debug("%s: if %s (%d): %s (prefixlen %d)",
+                   __func__, id->id_name, id->id_index,
+                   addr6tostr(&ifa6->sin6_addr), iac.ia_prefixlen);
+               break;
+       default:
+               return;
+       }
+
+       SLIST_FOREACH(ia, &id->id_ialist, ia_entry) {
+               if (ia->ia_af != iac.ia_af ||
+                   ia->ia_prefixlen != iac.ia_prefixlen ||
+                   iacmp(ia, &iac))
+                       continue;
+
+               /*
+                * Unregister the interface if this is a primary
+                * address, then check for new primary address.
+                */
+               if (ia->ia_af == AF_INET && ia == intf_primaryv4(id)) {
+                       vif4_unregister(id);
+                       if (intf_primaryv4(id) != NULL)
+                               regagain = 1;
+               }
+
+               SLIST_REMOVE(&id->id_ialist, ia, intf_addr, ia_entry);
+               free(ia);
+
+               /* Re-register if there is a new primary address. */
+               if (regagain)
+                       vif4_register(id);
+               return;
+       }
+}
+
+#define        ROUNDUP(a)      \
+    (((a) & (sizeof(long) - 1)) ? (1 + ((a) | (sizeof(long) - 1))) : (a))
+
+void
+get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
+{
+       int     i;
+
+       for (i = 0; i < RTAX_MAX; i++) {
+               if (addrs & (1 << i)) {
+                       rti_info[i] = sa;
+                       sa = (struct sockaddr *)((char *)(sa) +
+                           ROUNDUP(sa->sa_len));
+               } else
+                       rti_info[i] = NULL;
+       }
+}
+
+void
+rtmsg_process(const uint8_t *buf, size_t len)
+{
+       struct rt_msghdr        *rtm;
+       struct if_msghdr         ifm;
+       struct ifa_msghdr       *ifam;
+       struct sockaddr         *sa, *rti_info[RTAX_MAX];
+       size_t                   offset;
+       const uint8_t           *next;
+
+       for (offset = 0; offset < len; offset += rtm->rtm_msglen) {
+               next = buf + offset;
+               rtm = (struct rt_msghdr *)next;
+               if (len < offset + sizeof(unsigned short) ||
+                   len < offset + rtm->rtm_msglen)
+                       fatalx("%s: partial RTM in buffer", __func__);
+               if (rtm->rtm_version != RTM_VERSION)
+                       continue;
+
+               sa = (struct sockaddr *)(next + rtm->rtm_hdrlen);
+               get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
+
+               switch (rtm->rtm_type) {
+               case RTM_IFINFO:
+                       memcpy(&ifm, next, sizeof(ifm));
+                       if_update(ifm.ifm_index, ifm.ifm_flags, &ifm.ifm_data,
+                           (struct sockaddr_dl *)rti_info[RTAX_IFP]);
+                       break;
+               case RTM_NEWADDR:
+                       ifam = (struct ifa_msghdr *)rtm;
+                       if ((ifam->ifam_addrs & (RTA_NETMASK | RTA_IFA |
+                           RTA_BRD)) == 0)
+                               break;
+
+                       if_newaddr(ifam->ifam_index,
+                           (struct sockaddr *)rti_info[RTAX_IFA],
+                           (struct sockaddr *)rti_info[RTAX_NETMASK]);
+                       break;
+               case RTM_DELADDR:
+                       ifam = (struct ifa_msghdr *)rtm;
+                       if ((ifam->ifam_addrs & (RTA_NETMASK | RTA_IFA |
+                           RTA_BRD)) == 0)
+                               break;
+
+                       if_deladdr(ifam->ifam_index,
+                           (struct sockaddr *)rti_info[RTAX_IFA],
+                           (struct sockaddr *)rti_info[RTAX_NETMASK]);
+                       break;
+               case RTM_IFANNOUNCE:
+                       if_announce((struct if_announcemsghdr *)next);
+                       break;
+               default:
+                       break;
+               }
+       }
+}
diff --git log.c log.c
new file mode 100644
index 0000000..9a509fa
--- /dev/null
+++ log.c
@@ -0,0 +1,218 @@
+/*     $OpenBSD:$      */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henn...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <time.h>
+
+static int      debug;
+static int      verbose;
+const char     *log_procname;
+
+void   log_init(int, int);
+void   log_procinit(const char *);
+void   log_setverbose(int);
+int    log_getverbose(void);
+void   log_warn(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   log_warnx(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   log_info(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   log_debug(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   logit(int, const char *, ...)
+           __attribute__((__format__ (printf, 2, 3)));
+void   vlog(int, const char *, va_list)
+           __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+
+void
+log_init(int n_debug, int facility)
+{
+       extern char     *__progname;
+
+       debug = n_debug;
+       verbose = n_debug;
+       log_procinit(__progname);
+
+       if (!debug)
+               openlog(__progname, LOG_PID | LOG_NDELAY, facility);
+
+       tzset();
+}
+
+void
+log_procinit(const char *procname)
+{
+       if (procname != NULL)
+               log_procname = procname;
+}
+
+void
+log_setverbose(int v)
+{
+       verbose = v;
+}
+
+int
+log_getverbose(void)
+{
+       return (verbose);
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       vlog(pri, fmt, ap);
+       va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+       char    *nfmt;
+       int      saved_errno = errno;
+
+       if (debug) {
+               /* best effort in out of mem situations */
+               if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+                       vfprintf(stderr, fmt, ap);
+                       fprintf(stderr, "\n");
+               } else {
+                       vfprintf(stderr, nfmt, ap);
+                       free(nfmt);
+               }
+               fflush(stderr);
+       } else
+               vsyslog(pri, fmt, ap);
+
+       errno = saved_errno;
+}
+
+void
+log_warn(const char *emsg, ...)
+{
+       char            *nfmt;
+       va_list          ap;
+       int              saved_errno = errno;
+
+       /* best effort to even work in out of memory situations */
+       if (emsg == NULL)
+               logit(LOG_ERR, "%s", strerror(saved_errno));
+       else {
+               va_start(ap, emsg);
+
+               if (asprintf(&nfmt, "%s: %s", emsg,
+                   strerror(saved_errno)) == -1) {
+                       /* we tried it... */
+                       vlog(LOG_ERR, emsg, ap);
+                       logit(LOG_ERR, "%s", strerror(saved_errno));
+               } else {
+                       vlog(LOG_ERR, nfmt, ap);
+                       free(nfmt);
+               }
+               va_end(ap);
+       }
+
+       errno = saved_errno;
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+       va_list  ap;
+
+       va_start(ap, emsg);
+       vlog(LOG_ERR, emsg, ap);
+       va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+       va_list  ap;
+
+       va_start(ap, emsg);
+       vlog(LOG_INFO, emsg, ap);
+       va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+       va_list  ap;
+
+       if (verbose > 1) {
+               va_start(ap, emsg);
+               vlog(LOG_DEBUG, emsg, ap);
+               va_end(ap);
+       }
+}
+
+static void
+vfatalc(int code, const char *emsg, va_list ap)
+{
+       static char     s[BUFSIZ];
+       const char      *sep;
+
+       if (emsg != NULL) {
+               (void)vsnprintf(s, sizeof(s), emsg, ap);
+               sep = ": ";
+       } else {
+               s[0] = '\0';
+               sep = "";
+       }
+       if (code)
+               logit(LOG_CRIT, "%s: %s%s%s",
+                   log_procname, s, sep, strerror(code));
+       else
+               logit(LOG_CRIT, "%s%s%s", log_procname, sep, s);
+}
+
+void
+fatal(const char *emsg, ...)
+{
+       va_list ap;
+
+       va_start(ap, emsg);
+       vfatalc(errno, emsg, ap);
+       va_end(ap);
+       exit(1);
+}
+
+void
+fatalx(const char *emsg, ...)
+{
+       va_list ap;
+
+       va_start(ap, emsg);
+       vfatalc(0, emsg, ap);
+       va_end(ap);
+       exit(1);
+}
diff --git log.h log.h
new file mode 100644
index 0000000..b684405
--- /dev/null
+++ log.h
@@ -0,0 +1,48 @@
+/*     $OpenBSD:$      */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henn...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#include <sys/cdefs.h>
+
+#include <stdarg.h>
+#include <syslog.h>
+
+void   log_init(int, int);
+void   log_procinit(const char *);
+void   log_setverbose(int);
+int    log_getverbose(void);
+void   log_warn(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   log_warnx(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   log_info(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   log_debug(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+void   logit(int, const char *, ...)
+           __attribute__((__format__ (printf, 2, 3)));
+void   vlog(int, const char *, va_list)
+           __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+           __attribute__((__format__ (printf, 1, 2)));
+
+#endif /* LOG_H */
diff --git mcast-proxy.8 mcast-proxy.8
new file mode 100644
index 0000000..cddeab0
--- /dev/null
+++ mcast-proxy.8
@@ -0,0 +1,113 @@
+.\"    $OpenBSD:$
+.\"
+.\" Copyright (c) 2017 Rafael Zalamena <rzalam...@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and/or distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate$
+.Dt MCAST-PROXY 8
+.Os
+.Sh NAME
+.Nm mcast-proxy
+.Nd Multicast Proxy
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnv
+.Op Fl D Ar macro Ns = Ns Ar value
+.Op Fl f Ar file
+.Sh DESCRIPTION
+.Nm
+is a multicast proxy implementation for the Internet Group Management
+Protocol (IGMP) and Multicast Listener Discovery (MLD) protocols.
+It is used on networks that face the client to control the multicast
+traffic based on the interest of the local network and to reduce the
+load by filtering unneeded multicast traffic.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl D Ar macro Ns = Ns Ar value
+Define
+.Ar macro
+to be set to
+.Ar value
+on the command line.
+Overrides the definition of
+.Ar macro
+in the configuration file.
+.It Fl d
+Do not daemonize.
+If this option is specified,
+.Nm
+will run in the foreground and log to
+.Em stderr .
+.It Fl f Ar file
+Specify an alternative configuration file.
+.It Fl n
+Only check the configuration file for validity.
+.It Fl v
+Produce more verbose output.
+.El
+.Sh FILES
+.Bl -tag -width "/etc/mcast-proxy.confXX"
+.It Pa /etc/mcast-proxy.conf
+Default
+.Nm
+configuration file.
+.El
+.Sh SEE ALSO
+.Xr multicast 4 ,
+.Xr mcast-proxy.conf 5
+.Sh STANDARDS
+.Rs
+.%A S. Deering
+.%D August 1989
+.%R RFC 1112
+.%T Host Extensions for IP Multicasting
+.Re
+.Pp
+.Rs
+.%A W. Fenner
+.%D November 1997
+.%R RFC 2236
+.%T Internet Group Management Protocol, Version 2
+.Re
+.Pp
+.Rs
+.%A M. Christensen
+.%A Thrane & Thrane
+.%A K. Kimball
+.%A F. Solensky
+.%D May 2006
+.%R RFC 4541
+.%T Considerations for Internet Group Management Protocol (IGMP) and Multicast 
Listener Discovery (MLD) Snooping Switches
+.Re
+.Pp
+.Rs
+.%A B. Fenner
+.%A H. He
+.%A B. Haberman
+.%A H. Sandick
+.%D August 2006
+.%R RFC 4605
+.%T Internet Group Management Protocol (IGMP) / Multicast Listener Discovery 
(MLD)-Based Multicast Forwarding ("IGMP/MLD Proxying")
+.Re
+.Sh HISTORY
+The
+.Nm
+program first appeared in
+.Ox 6.2 .
+.Sh AUTHORS
+.An -nosplit
+.Nm
+was written by
+.An Rafael Zalamena Aq Mt rzalam...@openbsd.org
diff --git mcast-proxy.c mcast-proxy.c
new file mode 100644
index 0000000..ee92532
--- /dev/null
+++ mcast-proxy.c
@@ -0,0 +1,846 @@
+/*     $OpenBSD:$      */
+
+/*
+ * Copyright (c) 2017 Rafael Zalamena <rzalam...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <arpa/inet.h>
+
+#include <sys/time.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <netinet/igmp.h>
+
+#include <pwd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mcast-proxy.h"
+
+__dead void usage(void);
+__dead void daemon_shutdown(void);
+void sighandler(int, short, void *);
+void config_setdefaults(void);
+
+int mcast_mquery4(struct intf_data *, struct in_addr *, struct in_addr *);
+int mcast_mquery6(struct intf_data *, struct in6_addr *, struct in6_addr *);
+int build_packet(uint8_t *, size_t *, struct intf_data *, struct in_addr *,
+    struct in_addr *, uint8_t, uint8_t);
+int build_packet6(uint8_t *, size_t *, struct intf_data *,
+    struct in6_addr *, uint8_t, uint8_t);
+int kernel_parse(uint8_t *, size_t);
+int kernel_parsev6(uint8_t *, size_t);
+struct igmp *igmp_parse(uint8_t *, size_t *, struct sockaddr_storage *);
+const char *igmptypetostr(uint16_t);
+void intf_setup(void);
+void send_generalmquery(int, short, void *);
+void igmp_recv(int, short, void *);
+const char *mldtypetostr(uint16_t);
+int mld_parse(struct intf_data *, struct sockaddr_storage *, uint8_t *,
+    size_t);
+void mld_recv(int, short, void *);
+
+struct iflist           iflist;
+struct intf_data       *upstreamif;
+int                     igmpsd = -1;
+int                     mldsd = -1;
+
+const char             *config_file = "/etc/mcast-proxy.conf";
+struct igmpproxy_conf   ic;
+
+int
+main(int argc, char *argv[])
+{
+       struct passwd   *pw;
+       int              verbose = 0, daemonize = 1, noaction = 0;
+       int              ch, intfsd;
+       struct timeval   qtv;
+       struct event     igmpev, mldev, intfev, qtimerev;
+       struct event     hupev, termev, intev;
+
+       config_setdefaults();
+
+       /* Load all system interfaces and get their information. */
+       intfsd = intf_init();
+
+       /* Initiate with verbose logging. */
+       log_init(1, LOG_DAEMON);
+       log_setverbose(1);
+
+       while ((ch = getopt(argc, argv, "f:D:dnv")) != -1) {
+               switch (ch) {
+               case 'D':
+                       if (cmdline_symset(optarg) < 0)
+                               log_warnx("could not parse macro definition %s",
+                                    optarg);
+                       break;
+               case 'd':
+                       daemonize = 0;
+                       break;
+               case 'f':
+                       config_file = optarg;
+                       break;
+               case 'n':
+                       noaction = 1;
+                       break;
+               case 'v':
+                       verbose = 2;
+                       break;
+               default:
+                       usage();
+                       break;
+               }
+       }
+
+       if (parse_config(config_file) == -1)
+               fatalx("configuration failed");
+
+       if (noaction)
+               exit(0);
+
+       /* Assert that we can run multicast forwarding. */
+       assert_mcastforward();
+
+       /* Create the IGMP socket. */
+       if (ic.ic_ipv4)
+               igmpsd = open_igmp_socket();
+       if (ic.ic_ipv6)
+               mldsd = open_mld_socket();
+
+       /* Drop privileges. */
+       pw = getpwnam(IGMP_PROXY_USER);
+       if (pw == NULL)
+               fatal("getpwnam");
+
+       if (chroot(pw->pw_dir) == -1)
+               fatal("chroot");
+       if (chdir("/") == -1)
+               fatal("chdir");
+
+       if (setgroups(1, &pw->pw_gid) ||
+           setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+           setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+               fatal("privilege drop");
+
+       /* Use the configured logging verbosity. */
+       log_init(!daemonize, LOG_DAEMON);
+       log_setverbose(verbose);
+
+       if (daemonize)
+               daemon(0, 0);
+
+       log_info("startup");
+
+       /* Initialize libevent. */
+       event_init();
+
+       /* Install signal handlers. */
+       signal_set(&hupev, SIGHUP, sighandler, NULL);
+       signal_set(&intev, SIGINT, sighandler, NULL);
+       signal_set(&termev, SIGTERM, sighandler, NULL);
+       signal_add(&hupev, NULL);
+       signal_add(&intev, NULL);
+       signal_add(&termev, NULL);
+       signal(SIGPIPE, SIG_IGN);
+
+       event_set(&igmpev, igmpsd, EV_READ | EV_PERSIST,
+           igmp_recv, NULL);
+       event_add(&igmpev, NULL);
+       event_set(&mldev, mldsd, EV_READ | EV_PERSIST,
+           mld_recv, NULL);
+       event_add(&mldev, NULL);
+       event_set(&intfev, intfsd, EV_READ | EV_PERSIST,
+           intf_dispatch, NULL);
+       event_add(&intfev, NULL);
+
+       qtv.tv_sec = IGMP_STARTUP_QUERY_INTERVAL;
+       qtv.tv_usec = 0;
+       evtimer_set(&qtimerev, send_generalmquery, &qtimerev);
+       evtimer_add(&qtimerev, &qtv);
+
+       /* Initialize interfaces IGMP reception. */
+       intf_setup();
+
+#if 0
+       if (pledge("stdio inet", NULL) != 0)
+               fatal("pledge");
+#endif
+
+       /* Send the startup query. */
+       send_generalmquery(0, 0, &qtimerev);
+
+       event_dispatch();
+
+       daemon_shutdown();
+
+       return 0;
+}
+
+__dead void
+usage(void)
+{
+       extern const char       *__progname;
+
+       fprintf(stderr, "%s: [-dnv] [-D macro=value] [-f config]\n",
+           __progname);
+
+       exit(1);
+}
+
+__dead void
+daemon_shutdown(void)
+{
+       struct intf_data        *id;
+       int                      error = 0;
+
+       /* Clean up routes to make sure no interface references exist. */
+       mrt_cleanup();
+       upstreamif = NULL;
+
+       /* Remove all interfaces. */
+       while (!SLIST_EMPTY(&iflist)) {
+               id = SLIST_FIRST(&iflist);
+               id_free(id);
+       }
+
+       /* Close multicast sockets. */
+       error |= close_igmp_socket(igmpsd);
+       error |= close_mld_socket(mldsd);
+       igmpsd = -1;
+       mldsd = -1;
+
+       exit(error != 0);
+}
+
+void
+sighandler(int sig, __unused short ev, __unused void *arg)
+{
+       switch (sig) {
+       case SIGHUP:
+               /* FALLTHROUGH */
+       case SIGTERM:
+       case SIGINT:
+               log_info("received signal %d", sig);
+               daemon_shutdown();
+               break;
+       }
+}
+
+void
+config_setdefaults(void)
+{
+       ic.ic_ipv4 = 1;
+       ic.ic_ipv6 = 0;
+}
+
+const char *
+igmptypetostr(uint16_t type)
+{
+       switch (type) {
+       case IGMP_HOST_MEMBERSHIP_QUERY:
+               return "MEMBERSHIP_QUERY";
+       case IGMP_v1_HOST_MEMBERSHIP_REPORT:
+               return "MEMBERSHIP_REPORT_V1";
+       case IGMP_v2_HOST_MEMBERSHIP_REPORT:
+               return "MEMBERSHIP_REPORT_V2";
+       case IGMP_HOST_LEAVE_MESSAGE:
+               return "LEAVE";
+
+       default:
+               return "unknown";
+       }
+}
+
+int
+build_packet(uint8_t *p, size_t *plen, struct intf_data *id,
+    struct in_addr *dst, struct in_addr *grp, uint8_t type, uint8_t code)
+{
+       struct ip               *ip = (struct ip *)p;
+       struct intf_addr        *ia;
+       struct igmp             *igmp;
+       uint8_t                  hlen;
+
+       *plen = 0;
+
+       if ((ia = intf_primaryv4(id)) == NULL) {
+               log_debug("%s doesn't have an address", id->id_name);
+               return -1;
+       }
+
+       memset(ip, 0, sizeof(*ip));
+       hlen = sizeof(*ip) >> 2;
+       ip->ip_hl = hlen;
+       ip->ip_v = IPVERSION;
+       ip->ip_tos = IPTOS_PREC_INTERNETCONTROL;
+       ip->ip_ttl = IPDEFTTL;
+       ip->ip_p = IPPROTO_IGMP;
+       ip->ip_src = ia->ia_addr.v4;
+       ip->ip_dst = *dst;
+       *plen = hlen << 2;
+
+       igmp = (struct igmp *)(p + sizeof(*ip));
+       igmp->igmp_type = type;
+       igmp->igmp_code = code;
+       igmp->igmp_cksum = 0;
+       igmp->igmp_group = *grp;
+       *plen += sizeof(*igmp);
+
+       /* Calculate the IP checksum. */
+       ip->ip_len = htons(*plen);
+       ip->ip_sum = wrapsum(checksum((uint8_t *)ip, hlen, 0));
+
+       /* Calculate the IGMP checksum. */
+       igmp->igmp_cksum = wrapsum(checksum((uint8_t *)igmp,
+           sizeof(*igmp), 0));
+
+       return 0;
+}
+
+int
+mcast_mquery4(struct intf_data *id, struct in_addr *dst, struct in_addr *grp)
+{
+       struct intf_addr        *ia;
+       size_t                   blen;
+       ssize_t                  bsent;
+       struct sockaddr_storage  to;
+       uint8_t                  b[2048];
+
+       if ((ia = intf_primaryv4(id)) == NULL) {
+               log_debug("%s doesn't have an address", id->id_name);
+               return -1;
+       }
+
+       blen = sizeof(b);
+       if (build_packet(b, &blen, id, dst, grp,
+           IGMP_HOST_MEMBERSHIP_QUERY, IGMP_QUERY_INTERVAL) == -1) {
+               log_debug("%s: packet build failed", __func__);
+               return -1;
+       }
+
+       igmp_setif(id);
+
+       to.ss_family = AF_INET;
+       to.ss_len = sizeof(struct sockaddr_in);
+       sstosin(&to)->sin_addr = *dst;
+       if ((bsent = sendto(igmpsd, b, blen, 0, (struct sockaddr *)&to,
+           to.ss_len)) == -1) {
+               log_warn("send IGMP %s (via %s) to %s",
+                   addr4tostr(&ia->ia_addr.v4), id->id_name, addrtostr(&to));
+               return -1;
+       }
+
+       igmp_setif(NULL);
+
+       log_debug("%s (%s) -> %s IGMP MEMBERSHIP_QUERY %ld bytes",
+           addr4tostr(&ia->ia_addr.v4), id->id_name, addrtostr(&to),
+           bsent);
+
+       return 0;
+}
+
+int
+build_packet6(uint8_t *p, size_t *plen, struct intf_data *id,
+    struct in6_addr *grp, uint8_t type, uint8_t code)
+{
+       struct intf_addr        *ia;
+       struct mld_hdr          *mld;
+
+       *plen = 0;
+
+       if ((ia = intf_ipv6linklayer(id)) == NULL) {
+               log_debug("%s doesn't have an address", id->id_name);
+               return -1;
+       }
+
+       mld = (struct mld_hdr *)p;
+       mld->mld_type = type;
+       mld->mld_code = code;
+       mld->mld_cksum = 0;
+       mld->mld_maxdelay = 0;
+       mld->mld_reserved = 0;
+       mld->mld_addr = *grp;
+       *plen += sizeof(*mld);
+
+       return 0;
+}
+
+int
+mcast_mquery6(struct intf_data *id, struct in6_addr *dst,
+    struct in6_addr *grp)
+{
+       struct intf_addr        *ia;
+       struct cmsghdr          *cmsg;
+       struct in6_pktinfo      *ipi6;
+       size_t                   blen;
+       ssize_t                  bsent;
+       struct msghdr            msg;
+       struct sockaddr_storage  to;
+       struct iovec             iov[1];
+       uint8_t                  b[2048];
+       uint8_t                  cmsgbuf[
+           CMSG_SPACE(sizeof(struct in6_pktinfo))
+       ];
+
+       if ((ia = intf_ipv6linklayer(id)) == NULL) {
+               log_debug("%s doesn't have an address", id->id_name);
+               return -1;
+       }
+
+       blen = sizeof(b);
+       if (build_packet6(b, &blen, id, grp, MLD_LISTENER_QUERY, 0) == -1) {
+               log_debug("%s: packet build failed", __func__);
+               return -1;
+       }
+
+       to.ss_family = AF_INET6;
+       to.ss_len = sizeof(struct sockaddr_in6);
+       sstosin6(&to)->sin6_addr = *dst;
+
+       /* Populate msghdr. */
+       memset(&msg, 0, sizeof(msg));
+       iov[0].iov_base = b;
+       iov[0].iov_len = blen;
+       msg.msg_iov = iov;
+       msg.msg_iovlen = 1;
+       msg.msg_name = &to;
+       msg.msg_namelen = sizeof(struct sockaddr_in6);
+
+       /* Populate msghdr parameters. */
+       memset(cmsgbuf, 0, sizeof(cmsgbuf));
+       msg.msg_control = cmsgbuf;
+       msg.msg_controllen = sizeof(cmsgbuf);
+
+       /* Use the IPV6_PKTINFO to select the interface. */
+       cmsg = (struct cmsghdr *)CMSG_FIRSTHDR(&msg);
+       cmsg->cmsg_len = CMSG_LEN(sizeof(*ipi6));
+       cmsg->cmsg_level = IPPROTO_IPV6;
+       cmsg->cmsg_type = IPV6_PKTINFO;
+
+       /* Set output interface */
+       ipi6 = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+       ipi6->ipi6_ifindex = id->id_index;
+
+       if ((bsent = sendmsg(mldsd, &msg, 0)) == -1) {
+               log_warn("MLD %s (via %s) to %s",
+                   addr6tostr(&ia->ia_addr.v6), id->id_name, addrtostr(&to));
+               return -1;
+       }
+
+       log_debug("%s (%s) -> %s MLD MEMBERSHIP_QUERY %ld bytes",
+           addr6tostr(&ia->ia_addr.v6), id->id_name, addrtostr(&to),
+           bsent);
+
+       return 0;
+}
+
+int
+kernel_parse(uint8_t *p, size_t plen)
+{
+       struct intf_addr        *ia;
+       struct intf_data        *id;
+       struct ip               *ip = (struct ip *)p;
+       size_t                   hlen;
+
+       /* Sanity check: do we have enough data to work with? */
+       if (plen < sizeof(*ip)) {
+               log_debug("%s: insufficient packet size", __func__);
+               return 0;
+       }
+
+       /* Validate upstream interface current state. */
+       if (upstreamif == NULL) {
+               log_debug("%s: no upstream interface", __func__);
+               return 0;
+       }
+       if ((ia = intf_primaryv4(upstreamif)) == NULL) {
+               log_debug("%s: no upstream interface address", __func__);
+               return 0;
+       }
+
+       /* IP header validations. */
+       if (ip->ip_v != IPVERSION) {
+               log_debug("%s: wrong IP version", __func__);
+               return 0;
+       }
+       hlen = ip->ip_hl << 2;
+       if (hlen < sizeof(*ip)) {
+               log_debug("%s: wrong IP header length", __func__);
+               return 0;
+       }
+       if ((ip->ip_off & IP_OFFMASK) != 0) {
+               log_debug("%s: fragmented packet", __func__);
+               return 0;
+       }
+       if (ip->ip_ttl == 0) {
+               log_debug("%s: invalid TTL", __func__);
+               return 0;
+       }
+       if (ip->ip_src.s_addr == INADDR_ANY ||
+           ip->ip_dst.s_addr == INADDR_ANY) {
+               log_debug("%s: invalid packet addresses", __func__);
+               return 0;
+       }
+
+       /* We only handle kernel messages here. */
+       if (ip->ip_p != IPPROTO_IP)
+               return -1;
+
+       id = intf_lookupbyaddr4(ip->ip_src.s_addr);
+       if (id == NULL || !id->id_enabled) {
+               log_debug("%s: no interface matches origin", __func__);
+               return 0;
+       }
+
+       mrt_insert4(MV_IGMPV3, id, &ip->ip_src, &ip->ip_dst);
+
+       return 0;
+}
+
+struct igmp *
+igmp_parse(uint8_t *p, size_t *plen, struct sockaddr_storage *src)
+{
+       struct ip               *ip = (struct ip *)p;
+       size_t                   hlen, ptotal;
+       uint16_t                 cksum;
+
+       if (ip->ip_p != IPPROTO_IGMP) {
+               log_debug("%s: expected IGMP message, got %d",
+                   __func__, ip->ip_p);
+               return NULL;
+       }
+
+       hlen = ip->ip_hl << 2;
+
+       ptotal = ntohs(ip->ip_len);
+       if (*plen != ptotal) {
+               log_debug("%s: IP header length different than packet "
+                   "(%ld vs %ld)", __func__, ptotal, *plen);
+               return 0;
+       }
+
+       cksum = wrapsum(checksum((uint8_t *)ip, hlen, 0));
+       if (cksum != 0) {
+               log_debug("%s: IP checksum is invalid", __func__);
+               return NULL;
+       }
+
+       cksum = wrapsum(checksum((uint8_t *)ip, *plen, 0));
+       if (cksum != 0) {
+               log_debug("%s: IGMP invalid checksum", __func__);
+               return NULL;
+       }
+
+       log_debug("IGMP (IPv%d) %s -> %s %ld bytes",
+           ip->ip_v, addr4tostr(&ip->ip_src), addr4tostr(&ip->ip_dst),
+           *plen);
+
+       /* Return the source address and update the remaining size. */
+       memset(src, 0, sizeof(*src));
+       src->ss_family = AF_INET;
+       src->ss_len = sizeof(struct sockaddr_in);
+       sstosin(src)->sin_addr = ip->ip_src;
+
+       *plen -= hlen;
+
+       return ((struct igmp *)(p + hlen));
+}
+
+void
+igmp_recv(int fd, __unused short ev, __unused void *arg)
+{
+       struct igmp             *igmp;
+       struct intf_data        *id;
+       ssize_t                  rlen;
+       struct sockaddr_storage  src;
+       uint8_t                  p[2048];
+
+       if ((rlen = recv(fd, p, sizeof(p), 0)) == -1) {
+               log_warn("%s: recv", __func__);
+               return;
+       }
+       /* Check for kernel messages and do IP header validations. */
+       if (kernel_parse(p, rlen) == 0 ||
+           (igmp = igmp_parse(p, &rlen, &src)) == NULL)
+               return;
+
+       /* Handle the IGMP packet. */
+       if ((size_t)rlen < sizeof(*igmp)) {
+               log_debug("%s: IGMP packet too short", __func__);
+               return;
+       }
+
+       log_debug("  %s: code %d group %s",
+           igmptypetostr(igmp->igmp_type), igmp->igmp_code,
+           addr4tostr(&igmp->igmp_group));
+
+       /* Sanity check: group is always multicast address. */
+       if (!IN_MULTICAST(ntohl(igmp->igmp_group.s_addr))) {
+               log_debug("%s: group is not a multicast address",
+                   __func__);
+               return;
+       }
+
+       /* Determine from which interface this packet came from. */
+       id = intf_lookupbyaddr4(sstosin(&src)->sin_addr.s_addr);
+       if (id == NULL || !id->id_enabled) {
+               log_debug("%s: no interface matches origin", __func__);
+               return;
+       }
+
+       /* Don't receive commands from upstream interface. */
+       if (id == upstreamif) {
+               log_debug("%s: ignoring host command on upstream interface",
+                  __func__);
+               return;
+       }
+
+       switch (igmp->igmp_type) {
+       case IGMP_HOST_MEMBERSHIP_QUERY:
+               break;
+       case IGMP_v1_HOST_MEMBERSHIP_REPORT:
+               mrt_insert4(MV_IGMPV1, id, &sstosin(&src)->sin_addr,
+                   &igmp->igmp_group);
+               break;
+       case IGMP_v2_HOST_MEMBERSHIP_REPORT:
+               mrt_insert4(MV_IGMPV2, id, &sstosin(&src)->sin_addr,
+                   &igmp->igmp_group);
+               break;
+       case IGMP_HOST_LEAVE_MESSAGE:
+               mrt_remove4(id, &sstosin(&src)->sin_addr, &igmp->igmp_group);
+               break;
+       }
+}
+
+const char *
+mldtypetostr(uint16_t type)
+{
+       switch (type) {
+       case MLD_LISTENER_QUERY:
+               return "LISTENER_QUERY";
+       case MLD_LISTENER_REPORT:
+               return "LISTENER_REPORT";
+       case MLD_LISTENER_DONE:
+               return "LISTENER_DONE";
+
+       default:
+               return "unknown";
+       }
+}
+
+int
+kernel_parsev6(uint8_t *p, size_t plen)
+{
+       struct ip6_hdr          *ip6 = (struct ip6_hdr *)p;
+       struct intf_data        *id;
+
+       /* Sanity checks:
+        * - packet size (ipv6 header)
+        * - multicast destination
+        */
+       if (plen < sizeof(*ip6)) {
+               log_debug("%s: packet too small for IPv6 header", __func__);
+               return -1;
+       }
+       if (!IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
+               log_debug("%s: not a multicast packet", __func__);
+               return -1;
+       }
+
+       id = intf_lookupbyaddr6(&ip6->ip6_src);
+       if (id == NULL || !id->id_enabled) {
+               log_debug("%s: no input interface for %s",
+                   __func__, addr6tostr(&ip6->ip6_src));
+               return -1;
+       }
+
+       log_debug("IPv6 %s (%s) -> %s",
+           addr6tostr(&ip6->ip6_src), id->id_name,
+           addr6tostr(&ip6->ip6_dst));
+
+       mrt_insert6(MV_IGMPV3, id, &ip6->ip6_src, &ip6->ip6_dst);
+
+       return 0;
+}
+
+int
+mld_parse(struct intf_data *id, struct sockaddr_storage *src,
+    uint8_t *p, size_t plen)
+{
+       struct mld_hdr                  *mld = (struct mld_hdr *)p;
+
+       if (plen < sizeof(*mld)) {
+               log_debug("%s: packet too small", __func__);
+               return -1;
+       }
+
+       log_debug("MLD %s %s -> %s", mldtypetostr(mld->mld_type),
+           addrtostr(src), addr6tostr(&mld->mld_addr));
+
+       switch (mld->mld_type) {
+       case MLD_LISTENER_QUERY:
+               break;
+       case MLD_LISTENER_REPORT:
+               mrt_insert6(MV_IGMPV2, id, &sstosin6(src)->sin6_addr,
+                   &mld->mld_addr);
+               break;
+       case MLD_LISTENER_DONE:
+               mrt_remove6(id, &sstosin6(src)->sin6_addr, &mld->mld_addr);
+               break;
+
+       default:
+               log_debug("%s: invalid MLD type %d",
+                   __func__, mld->mld_type);
+               break;
+       }
+
+       return 0;
+}
+
+void
+mld_recv(int sd, __unused short ev, __unused void *arg)
+{
+       struct in6_pktinfo              *ipi6 = NULL;
+       struct intf_data                *id;
+       struct cmsghdr                  *cmsg;
+       ssize_t                          rlen;
+       struct msghdr                    msg;
+       struct iovec                     iov[1];
+       struct sockaddr_storage          ss;
+       uint8_t                          iovbuf[2048];
+       uint8_t                          cmsgbuf[
+               CMSG_SPACE(sizeof(*ipi6))
+       ];
+
+       iov[0].iov_base = iovbuf;
+       iov[0].iov_len = sizeof(iovbuf);
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = cmsgbuf;
+       msg.msg_controllen = sizeof(cmsgbuf);
+       msg.msg_name = &ss;
+       msg.msg_namelen = sizeof(ss);
+       if ((rlen = recvmsg(sd, &msg, 0)) == -1) {
+               log_warn("%s: recvmsg", __func__);
+               return;
+       }
+
+       /* Sanity check: is this IPv6? */
+       if (ss.ss_family != AF_INET6) {
+               log_debug("%s: received non IPv6 packet", __func__);
+               return;
+       }
+
+       /* Find out input interface. */
+       for (cmsg = (struct cmsghdr *)CMSG_FIRSTHDR(&msg); cmsg;
+           cmsg = (struct cmsghdr *)CMSG_NXTHDR(&msg, cmsg)) {
+               if (cmsg->cmsg_level != IPPROTO_IPV6)
+                       continue;
+
+               switch (cmsg->cmsg_type) {
+               case IPV6_PKTINFO:
+                       ipi6 = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+                       break;
+               }
+       }
+       /* Kernel messages from the routing socket don't have PKTINFO. */
+       if (ipi6 == NULL) {
+               kernel_parsev6(iovbuf, rlen);
+               return;
+       }
+
+       /* Deal with packets coming from the network. */
+       id = intf_lookupbyindex(ipi6->ipi6_ifindex);
+       if (id == NULL || !id->id_enabled) {
+               log_debug("%s: no input interface for %s",
+                   __func__, addrtostr(&ss));
+               return;
+       }
+
+       /* Don't receive commands from upstream interface. */
+       if (id == upstreamif) {
+               log_debug("%s: ignoring host on upstream interface",
+                   __func__);
+               return;
+       }
+
+       mld_parse(id, &ss, iovbuf, rlen);
+}
+
+void
+send_generalmquery(__unused int sd, short ev, void *arg)
+{
+       struct event                    *qtimerev = (struct event *)arg;
+       struct intf_data                *id;
+       struct timeval                   qtv = { IGMP_QUERY_INTERVAL, 0 };
+       struct in_addr                   allhostsgrp, zerogrp;
+       struct in6_addr                  allhostsgrp6 =
+           IN6ADDR_LINKLOCAL_ALLNODES_INIT;
+       struct in6_addr                  zerogrp6 = IN6ADDR_ANY_INIT;
+
+       allhostsgrp.s_addr = htonl(INADDR_ALLHOSTS_GROUP);
+       zerogrp.s_addr = 0;
+
+       SLIST_FOREACH(id, &iflist, id_entry) {
+               /* Only join downstream interfaces. */
+               if (id->id_dir != IDIR_DOWNSTREAM)
+                       continue;
+
+               if (id->id_mv4)
+                       mcast_mquery4(id, &allhostsgrp, &zerogrp);
+               if (id->id_mv6)
+                       mcast_mquery6(id, &allhostsgrp6, &zerogrp6);
+       }
+
+       /* Only start timers if not called manually. */
+       if ((ev & EV_TIMEOUT) == EV_TIMEOUT) {
+               evtimer_add(qtimerev, &qtv);
+               mrt_querytimeradd();
+       }
+}
+
+void
+intf_setup(void)
+{
+       struct intf_data                *id;
+
+       SLIST_FOREACH(id, &iflist, id_entry) {
+               /* Disable IPv4 multicast if disabled globally. */
+               if (ic.ic_ipv4 == 0)
+                       id->id_mv4 = 0;
+               /* Disable IPv6 multicast if disabled globally. */
+               if (ic.ic_ipv6 == 0)
+                       id->id_mv6 = 0;
+
+               if (id->id_dir == IDIR_DISABLE)
+                       continue;
+
+               /* Register all enabled interfaces. */
+               vif_register(id);
+
+               if (id->id_dir != IDIR_DOWNSTREAM)
+                       continue;
+
+               /* Only join downstream interfaces. */
+               mcast_join(id, NULL);
+       }
+}
diff --git mcast-proxy.conf.5 mcast-proxy.conf.5
new file mode 100644
index 0000000..1400eef
--- /dev/null
+++ mcast-proxy.conf.5
@@ -0,0 +1,138 @@
+.\"    $OpenBSD:$
+.\"
+.\" Copyright (c) 2017 Rafael Zalamena <rzalam...@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate$
+.Dt MCAST-PROXY.CONF 5
+.Os
+.Sh NAME
+.Nm mcast-proxy.conf
+.Nd Multicast Proxy configuration file
+.Sh DESCRIPTION
+The
+.Xr mcast-proxy 8
+daemon implements IGMP/MLD proxy for multicast routing.
+.Sh SECTIONS
+The
+.Nm
+config file is divided into three main sections.
+.Bl -tag -width xxxx
+.It Sy Macros
+User-defined variables may be defined and used later, simplifying the
+configuration file.
+.It Sy Global Configuration
+Global settings for
+.Xr mcast-proxy 8 .
+Allows the configuration of globally supported Internet Protocols
+versions: IPv4 and/or IPv6.
+.It Sy Interfaces Configuration
+Interface-specific parameters.
+.El
+.Pp
+Argument names not beginning with a letter, digit, or underscore
+must be quoted.
+.Pp
+Additional configuration files can be included with the
+.Ic include
+keyword, for example:
+.Bd -literal -offset indent
+include "/etc/mcast-proxy.sub.conf"
+.Ed
+.Sh MACROS
+Macros can be defined that will later be expanded in context.
+Macro names must start with a letter, digit, or underscore,
+and may contain any of those characters.
+Macro names may not be reserved words (for example,
+.Ic upstreamif ,
+.Ic interface ,
+or
+.Ic default-threshold ) .
+Macros are not expanded inside quotes.
+.Pp
+For example:
+.Bd -literal -offset indent
+upstreamif="em0"
+default_threshold="1"
+interface $upstreamif {
+       threshold $default_threshold
+       upstream
+}
+.Ed
+.Sh GLOBAL CONFIGURATION
+Here are the settings that can be set globally:
+.Bl -tag -width Ds
+.It Ic ipv4 Pq Ic yes Ns | Ns Ic no
+Determines if the mcast-proxy will be enabled for IPv4.
+This setting is enabled by default.
+.It Ic ipv6 Pq Ic yes Ns | Ns Ic no
+Determines if MLD-proxy will be enabled for IPv6.
+This setting is disabled by default.
+.El
+.Sh INTERFACES CONFIGURATION
+This section will describe the interface multicast configuration
+options.
+An interface is specified by its name.
+.Bd -literal -offset indent
+interface em0 {
+       ...
+}
+.Ed
+.Pp
+Interface-specific parameters are listed below.
+.Bl -tag -width Ds
+.It Ic ipv4 Pq Ic yes Ns | Ns Ic no
+Enables or disables IPv4 support in this interface.
+The default value is inherited from the global configuration.
+.It Ic ipv6 Pq Ic yes Ns | Ns Ic no
+Enables or disables IPv6 support in this interface.
+The default value is inherited from the global configuration.
+.It Ic threshold Ar number
+Specify the minimum TTL required in the incoming packets to be
+forwarded (IPv4 only). The default value is 1.
+.It Ic source Ar network Ns / Ns Ar prefix
+Specify an alternate network to receive multicast from.
+By default only multicast traffic coming from the same network of the
+interface will be allowed.
+.It Pq Ic disabled Ns | Ns Ic downstream Ns | Ns Ic upstream
+Configure the interface role in the multicast proxying setup.
+.Ar disabled
+will disable the interface participation,
+.Ar downstream
+mark client facing interfaces and
+.Ar upstream
+mark the interface which will receive the multicast traffic.
+.Pp
+By default all interfaces are
+.Ar disabled .
+.El
+.Sh FILES
+.Bl -tag -width "/etc/mcast-proxy.conf" -compact
+.It Pa /etc/mcast-proxy.conf
+.Xr mcast-proxy 8
+configuration file
+.El
+.Sh SEE ALSO
+.Xr mcast-proxy 8 ,
+.Xr rc.conf.local 8
+.Sh HISTORY
+The
+.Nm
+file format first appeared in
+.Ox 6.2 .
+.Sh AUTHORS
+The
+.Xr mcast-proxy 8
+program was written by
+.An Rafael Zalamena Aq Mt rzalam...@openbsd.org .
diff --git mcast-proxy.h mcast-proxy.h
new file mode 100644
index 0000000..eac6ac5
--- /dev/null
+++ mcast-proxy.h
@@ -0,0 +1,214 @@
+/*     $OpenBSD:$      */
+
+/*
+ * Copyright (c) 2017 Rafael Zalamena <rzalam...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IGMP_PROXY_H
+#define IGMP_PROXY_H
+
+#include <arpa/inet.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <netinet/in.h>
+#include <net/if.h>
+
+#include <event.h>
+
+#include "log.h"
+
+#define IGMP_PROXY_USER                        "_dhcp"
+
+/* RFC 2236 section 8: value definitions. */
+#define IGMP_QUERY_INTERVAL            125 /* 125 seconds. */
+#define IGMP_RESPONSE_INTERVAL         10 /* 10 seconds. */
+#define IGMP_ROBUSTNESS_DEFVALUE       2
+#define IGMP_STARTUP_QUERY_INTERVAL (IGMP_QUERY_INTERVAL * 0.25)
+/*
+ * RFC 2236 Section 8.4: Group membership interval.
+ * Group membership interval is composed by the following formula:
+ * (Robustness * Query_Interval) + Query_Response_Interval.
+ */
+#define IGMP_GROUP_MEMBERSHIP_INTERVAL(r, q) \
+       (((r) * (q)) + IGMP_RESPONSE_INTERVAL)
+
+/* Signalize invalid virtual/multicast interface index. */
+#define INVALID_VINDEX ((uint16_t)-1)
+
+/* Interface direction configuration values. */
+enum intf_direction {
+       IDIR_DISABLE = 0,
+       IDIR_DOWNSTREAM,
+       IDIR_UPSTREAM,
+};
+
+enum mr_version {
+       MV_UNKNOWN,
+       MV_IGMPV1,
+       MV_IGMPV2, /* or MLDv1. */
+       MV_IGMPV3, /* or MLDv2. */
+};
+
+union uaddr {
+       struct in_addr          v4;
+       struct in6_addr         v6;
+};
+
+struct intf_addr {
+       SLIST_ENTRY(intf_addr)   ia_entry;
+       int                      ia_af;
+       union uaddr              ia_addr;
+       uint8_t                  ia_prefixlen;
+};
+SLIST_HEAD(ialist, intf_addr);
+
+struct intf_data {
+       SLIST_ENTRY(intf_data)           id_entry;
+
+       /* Interface status. */
+       int                              id_enabled;
+       /* Interface name. */
+       char                             id_name[IFNAMSIZ];
+       /* Interface index. */
+       unsigned int                     id_index;
+       /* Interface rdomain. */
+       unsigned int                     id_rdomain;
+       /* Interface flags. */
+       unsigned int                     id_flags;
+       /* Interface IPv4 list. */
+       struct ialist                    id_ialist;
+       /* Interface alternative networks. */
+       struct ialist                    id_altnetlist;
+
+       /* Multicast configurations. */
+
+       /* Virtual interface index. */
+       uint16_t                         id_vindex;
+       /* Virtual IPv6 interface index. */
+       uint16_t                         id_vindex6;
+       /* Interface direction configuration. */
+       enum intf_direction              id_dir;
+       /* Acceptable TTL threshold. */
+       uint8_t                          id_ttl;
+       /* Use IPv4 multicast. */
+       int                              id_mv4;
+       /* Use IPv6 multicast. */
+       int                              id_mv6;
+};
+SLIST_HEAD(iflist, intf_data);
+
+struct multicast_origin {
+       LIST_ENTRY(multicast_origin) mo_entry;
+       int                      mo_alive;
+       int                      mo_af;
+       struct intf_data        *mo_id;
+       union uaddr              mo_addr;
+};
+LIST_HEAD(molist, multicast_origin);
+
+struct igmpproxy_conf {
+       int                      ic_ipv4;
+       int                      ic_ipv6;
+};
+
+/* igmp-proxy.c */
+extern struct intf_data                *upstreamif;
+extern struct iflist            iflist;
+extern int                      igmpsd;
+extern int                      mldsd;
+extern struct igmpproxy_conf    ic;
+
+/* kroute.c */
+void assert_mcastforward(void);
+int intf_init(void);
+int igmp_setif(struct intf_data *);
+int vif_register(struct intf_data *);
+int vif_unregister(struct intf_data *);
+int vif4_register(struct intf_data *);
+int vif4_unregister(struct intf_data *);
+int vif6_register(struct intf_data *);
+int vif6_unregister(struct intf_data *);
+void intf_dispatch(int, short, void *);
+void intf_load(void);
+int open_igmp_socket(void);
+int close_igmp_socket(int);
+int open_mld_socket(void);
+int close_mld_socket(int);
+int mcast_join(struct intf_data *, struct sockaddr_storage *);
+int mcast_leave(struct intf_data *, struct sockaddr_storage *);
+int mcast4_join(struct intf_data *, struct in_addr *);
+int mcast4_leave(struct intf_data *, struct in_addr *);
+int mcast6_join(struct intf_data *, struct in6_addr *);
+int mcast6_leave(struct intf_data *, struct in6_addr *);
+int mcast_addroute(unsigned short, union uaddr *, union uaddr *,
+    struct molist *);
+int mcast_addroute6(unsigned short, union uaddr *, union uaddr *,
+    struct molist *);
+int mcast_delroute(unsigned short, union uaddr *, union uaddr *);
+int mcast_delroute6(unsigned short, union uaddr *, union uaddr *);
+
+/* util.c */
+const char *addrtostr(struct sockaddr_storage *);
+const char *addr4tostr(struct in_addr *);
+const char *addr6tostr(struct in6_addr *);
+int id_matchaddr4(struct intf_data *, uint32_t);
+int id_matchaddr6(struct intf_data *, struct in6_addr *);
+uint16_t checksum(uint8_t *, uint16_t, uint32_t);
+uint16_t wrapsum(uint16_t);
+struct intf_data *id_insert(unsigned short);
+struct intf_data *id_new(void);
+void id_free(struct intf_data *);
+void ia_inserttail(struct ialist *, struct intf_addr *);
+struct intf_data *intf_lookupbyname(const char *);
+struct intf_data *intf_lookupbyaddr4(uint32_t);
+struct intf_data *intf_lookupbyaddr6(struct in6_addr *);
+struct intf_data *intf_lookupbyindex(unsigned short);
+struct intf_addr *intf_primaryv4(struct intf_data *);
+struct intf_addr *intf_ipv6linklayer(struct intf_data *);
+uint8_t mask2prefixlen(in_addr_t);
+uint8_t mask2prefixlen6(struct sockaddr_in6 *);
+in_addr_t prefixlen2mask(uint8_t);
+void applymask(int, union uaddr *, const union uaddr *, int);
+
+/* mrt.c */
+void mrt_querytimeradd(void);
+struct multicast_route *mrt_insert4(enum mr_version, struct intf_data *,
+    struct in_addr *, struct in_addr *);
+struct multicast_route *mrt_insert6(enum mr_version, struct intf_data *,
+    struct in6_addr *, struct in6_addr *);
+void mrt_remove4(struct intf_data *, struct in_addr *, struct in_addr *);
+void mrt_remove6(struct intf_data *, struct in6_addr *, struct in6_addr *);
+void mrt_cleanup(void);
+
+/* parse.y */
+int cmdline_symset(const char *);
+int parse_config(const char *);
+
+/* Helpers */
+static inline struct sockaddr_in *
+sstosin(struct sockaddr_storage *ss)
+{
+       return (struct sockaddr_in *)ss;
+}
+
+static inline struct sockaddr_in6 *
+sstosin6(struct sockaddr_storage *ss)
+{
+       return (struct sockaddr_in6 *)ss;
+}
+
+#endif /* IGMP_PROXY_H */
diff --git mrt.c mrt.c
new file mode 100644
index 0000000..df80b75
--- /dev/null
+++ mrt.c
@@ -0,0 +1,577 @@
+/*     $OpenBSD:$      */
+
+/*
+ * Copyright (c) 2017 Rafael Zalamena <rzalam...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/tree.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "mcast-proxy.h"
+
+enum mr_state {
+       MS_NOTJOINED,
+       MS_JOINED,
+};
+
+struct multicast_route {
+       RB_ENTRY(multicast_route) mr_entry;
+
+       enum mr_state    mr_state;
+       enum mr_version  mr_version;
+       int              mr_af;
+       union uaddr      mr_group;
+       struct event     mr_timer;
+       /* Version timer. */
+       struct event     mr_vtimer;
+       /* Lowest version recorded during the version timer. */
+       enum mr_version  mr_lowestversion;
+       struct intf_data *mr_upstream;
+
+       /* Origin list. */
+       struct molist    mr_molist;
+};
+RB_HEAD(mrtree, multicast_route) mrtree = RB_INITIALIZER(&mrtree);
+
+struct multicast_origin *mo_lookup(struct molist *, struct intf_data *,
+    union uaddr *);
+struct multicast_origin *mrt_addorigin(struct multicast_route *,
+    struct intf_data *,union uaddr *);
+void _mrt_delorigin(struct multicast_route *, struct multicast_origin *);
+void mrt_delorigin(struct multicast_route *, struct intf_data *,
+    union uaddr *);
+
+void mrt_timeradd(struct event *);
+void mrt_timer(int, short, void *);
+void mrt_vtimeradd(struct multicast_route *);
+void mrt_vtimer(int, short, void *);
+struct multicast_route *mrt_new(void);
+void mrt_free(struct multicast_route *);
+struct multicast_route *mrt_find4(struct in_addr *);
+struct multicast_route *mrt_find6(struct in6_addr *);
+int mrcmp(struct multicast_route *, struct multicast_route *);
+RB_PROTOTYPE(mrtree, multicast_route, mr_entry, mrcmp);
+void mrt_nextstate(struct multicast_route *);
+
+struct multicast_origin *
+mo_lookup(struct molist *molist, struct intf_data *id, union uaddr *addr)
+{
+       struct multicast_origin *mo;
+       size_t                   addrsize;
+
+       LIST_FOREACH(mo, molist, mo_entry) {
+               addrsize = (mo->mo_af == AF_INET) ?
+                       sizeof(addr->v4) : sizeof(addr->v6);
+               if (id != NULL && id != mo->mo_id)
+                       continue;
+               if (memcmp(addr, &mo->mo_addr, addrsize) != 0)
+                       continue;
+
+               return mo;
+       }
+
+       return NULL;
+}
+
+struct multicast_origin *
+mrt_addorigin(struct multicast_route *mr, struct intf_data *id,
+    union uaddr *addr)
+{
+       struct multicast_origin *mo;
+
+       mo = mo_lookup(&mr->mr_molist, id, addr);
+       if (mo != NULL) {
+               /* Update the kernel routes in case they have expired. */
+               if (mr->mr_upstream != NULL) {
+                       if (mo->mo_af == AF_INET)
+                               mcast_addroute(mr->mr_upstream->id_vindex,
+                                   addr, &mr->mr_group, &mr->mr_molist);
+                       else
+                               mcast_addroute6(mr->mr_upstream->id_vindex6,
+                                   addr, &mr->mr_group, &mr->mr_molist);
+               }
+               mo->mo_alive = 1;
+               return mo;
+       }
+
+       mo = calloc(1, sizeof(*mo));
+       if (mo == NULL) {
+               log_warn("%s: calloc", __func__);
+               return NULL;
+       }
+
+       LIST_INSERT_HEAD(&mr->mr_molist, mo, mo_entry);
+
+       mo->mo_alive = 1;
+       mo->mo_id = id;
+       mo->mo_af = mr->mr_af;
+       mo->mo_addr = *addr;
+       if (id == upstreamif || mr->mr_upstream) {
+               if (mr->mr_upstream == NULL)
+                       mr->mr_upstream = upstreamif;
+
+               if (mo->mo_af == AF_INET)
+                       mcast_addroute(mr->mr_upstream->id_vindex, addr,
+                           &mr->mr_group, &mr->mr_molist);
+               else
+                       mcast_addroute6(mr->mr_upstream->id_vindex6, addr,
+                           &mr->mr_group, &mr->mr_molist);
+       }
+
+       return mo;
+}
+
+void
+_mrt_delorigin(struct multicast_route *mr, struct multicast_origin *mo)
+{
+       LIST_REMOVE(mo, mo_entry);
+
+       if (mr->mr_upstream != NULL) {
+               /*
+                * If this was the last item of the group list we can
+                * uninstall the whole group, otherwise update the
+                * installed routes with the current origins.
+                */
+               if (LIST_EMPTY(&mr->mr_molist)) {
+                       if (mo->mo_af == AF_INET)
+                               mcast_delroute(mr->mr_upstream->id_vindex,
+                                   &mo->mo_addr, &mr->mr_group);
+                       else
+                               mcast_delroute6(mr->mr_upstream->id_vindex6,
+                                   &mo->mo_addr, &mr->mr_group);
+               } else {
+                       if (mo->mo_af == AF_INET)
+                               mcast_addroute(mr->mr_upstream->id_vindex,
+                                   &mo->mo_addr, &mr->mr_group,
+                                   &mr->mr_molist);
+                       else
+                               mcast_addroute6(mr->mr_upstream->id_vindex6,
+                                   &mo->mo_addr, &mr->mr_group,
+                                   &mr->mr_molist);
+               }
+       }
+
+       free(mo);
+}
+
+void
+mrt_delorigin(struct multicast_route *mr, struct intf_data *id,
+    union uaddr *addr)
+{
+       struct multicast_origin *mo;
+
+       mo = mo_lookup(&mr->mr_molist, id, addr);
+       if (mo == NULL)
+               return;
+
+       _mrt_delorigin(mr, mo);
+}
+
+void
+mrt_timeradd(struct event *ev)
+{
+       unsigned long    total = IGMP_GROUP_MEMBERSHIP_INTERVAL(
+           IGMP_ROBUSTNESS_DEFVALUE, IGMP_RESPONSE_INTERVAL);
+       struct timeval   tv;
+
+       if (evtimer_pending(ev, &tv))
+               evtimer_del(ev);
+
+       tv.tv_sec = total;
+       tv.tv_usec = 0;
+       evtimer_add(ev, &tv);
+}
+
+void
+mrt_querytimeradd(void)
+{
+       struct multicast_route  *mr;
+
+       /* Activate all group expire timers. */
+       RB_FOREACH(mr, mrtree, &mrtree) {
+               mrt_timeradd(&mr->mr_timer);
+       }
+}
+
+void
+mrt_vtimeradd(struct multicast_route *mr)
+{
+       mrt_timeradd(&mr->mr_vtimer);
+}
+
+void
+mrt_timer(__unused int sd, __unused short ev, void *arg)
+{
+       struct multicast_route  *mr = arg;
+       struct multicast_origin *mo, *mon;
+
+       if (mr->mr_af == AF_INET)
+               log_debug("%s: group %s timer expired",
+                   __func__, addr4tostr(&mr->mr_group.v4));
+       else
+               log_debug("%s: group %s timer expired",
+                   __func__, addr6tostr(&mr->mr_group.v6));
+
+       /* Remove origins that did not respond. */
+       LIST_FOREACH_SAFE(mo, &mr->mr_molist, mo_entry, mon) {
+               if (mo->mo_alive) {
+                       /* Mark as dead until next update. */
+                       mo->mo_alive = 0;
+                       continue;
+               }
+
+               _mrt_delorigin(mr, mo);
+       }
+
+       mrt_nextstate(mr);
+
+       /* Remove the group if there is no more origins. */
+       if (LIST_EMPTY(&mr->mr_molist))
+               mrt_free(mr);
+}
+
+void
+mrt_vtimer(__unused int sd, __unused short ev, void *arg)
+{
+       struct multicast_route  *mr = arg;
+
+       if (mr->mr_af == AF_INET)
+               log_debug("%s: group %s version timer expired",
+                   __func__, addr4tostr(&mr->mr_group.v4));
+       else
+               log_debug("%s: group %s version timer expired",
+                   __func__, addr6tostr(&mr->mr_group.v6));
+
+       mrt_vtimeradd(mr);
+
+       /*
+        * Apply the RFC 2236 section 5 and RFC 4541 section 2.1.1 sub
+        * item 1: the IGMPv2 is the most compatible version of the
+        * protocol.
+        *
+        * This is the default fallback version.
+        */
+       if (mr->mr_version == MV_IGMPV2)
+               return;
+
+       /*
+        * If we are on a 'special' version, reset the lowest value and
+        * expect another report with a version different than v2. If no
+        * new reports with different version comes in, assume that
+        * there are no more to enter a compatibility mode.
+        */
+       mr->mr_version = mr->mr_lowestversion;
+       mr->mr_lowestversion = MV_IGMPV2;
+}
+
+struct multicast_route *
+mrt_new(void)
+{
+       struct multicast_route  *mr;
+
+       mr = calloc(1, sizeof(*mr));
+       if (mr == NULL) {
+               log_warn("%s: calloc", __func__);
+               return NULL;
+       }
+
+       mr->mr_state = MS_NOTJOINED;
+       mr->mr_version = MV_IGMPV3;
+       mr->mr_lowestversion = MV_IGMPV3;
+       LIST_INIT(&mr->mr_molist);
+
+       evtimer_set(&mr->mr_timer, mrt_timer, mr);
+       evtimer_set(&mr->mr_vtimer, mrt_vtimer, mr);
+       mrt_timeradd(&mr->mr_timer);
+       mrt_timeradd(&mr->mr_vtimer);
+
+       return mr;
+}
+
+void
+mrt_free(struct multicast_route *mr)
+{
+       struct multicast_origin *mo;
+       struct timeval           tv;
+       struct sockaddr_storage  ss;
+
+       if (evtimer_pending(&mr->mr_timer, &tv))
+               evtimer_del(&mr->mr_timer);
+
+       if (evtimer_pending(&mr->mr_vtimer, &tv))
+               evtimer_del(&mr->mr_vtimer);
+
+       while (!LIST_EMPTY(&mr->mr_molist)) {
+               mo = LIST_FIRST(&mr->mr_molist);
+               LIST_REMOVE(mo, mo_entry);
+               _mrt_delorigin(mr, mo);
+       }
+
+       ss.ss_family = mr->mr_af;
+       if (ss.ss_family == AF_INET)
+               sstosin(&ss)->sin_addr = mr->mr_group.v4;
+       else
+               sstosin6(&ss)->sin6_addr = mr->mr_group.v6;
+
+       log_debug("%s: remove group %s", __func__, addrtostr(&ss));
+
+       RB_REMOVE(mrtree, &mrtree, mr);
+
+       free(mr);
+}
+
+void
+mrt_cleanup(void)
+{
+       struct multicast_route  *mr;
+
+       while (!RB_EMPTY(&mrtree)) {
+               mr = RB_ROOT(&mrtree);
+               mrt_free(mr);
+       }
+}
+
+struct multicast_route *
+mrt_find4(struct in_addr *in)
+{
+       struct multicast_route   key;
+
+       memset(&key, 0, sizeof(key));
+       key.mr_af = AF_INET;
+       key.mr_group.v4 = *in;
+       return RB_FIND(mrtree, &mrtree, &key);
+}
+
+struct multicast_route *
+mrt_insert4(enum mr_version mv, struct intf_data *id,
+    struct in_addr *origin, struct in_addr *group)
+{
+       struct multicast_route  *mr, *mrn;
+       union uaddr              uorigin;
+
+       /* Sanity check: only use multicast groups. */
+       if (!IN_MULTICAST(ntohl(group->s_addr))) {
+               log_debug("%s(%s, %s): not multicast group",
+                   __func__, id->id_name, addr4tostr(group));
+               return NULL;
+       }
+
+       /* Try to find it, if it exists just add the new origin. */
+       mr = mrt_find4(group);
+       if (mr != NULL)
+               goto add_origin;
+
+       /* Otherwise create one and insert. */
+       mr = mrt_new();
+       if (mr == NULL)
+               return NULL;
+
+       mr->mr_af = AF_INET;
+       mr->mr_group.v4 = *group;
+       mrn = RB_INSERT(mrtree, &mrtree, mr);
+       if (mrn != NULL) {
+               mrt_free(mr);
+               mr = mrn;
+       }
+
+ add_origin:
+       /*
+        * Always use the lowest version immediately, otherwise wait the
+        * query timeout before switching. See mrt_vtimer() for more
+        * details.
+        */
+       if (mr->mr_version > mv)
+               mr->mr_version = mv;
+       if (mr->mr_lowestversion > mv)
+               mr->mr_lowestversion = mv;
+
+       uorigin.v4 = *origin;
+       mrt_addorigin(mr, id, &uorigin);
+
+       mrt_nextstate(mr);
+
+       return mr;
+}
+
+void
+mrt_remove4(struct intf_data *id, struct in_addr *origin,
+    struct in_addr *group)
+{
+       struct multicast_route  *mr;
+       union uaddr              uorigin;
+
+       mr = mrt_find4(group);
+       if (mr == NULL)
+               return;
+
+       /* IGMPv1 compatibility mode does not accept fast-leave. */
+       if (mr->mr_version == MV_IGMPV1)
+               return;
+
+       uorigin.v4 = *origin;
+       mrt_delorigin(mr, id, &uorigin);
+       mrt_nextstate(mr);
+       if (LIST_EMPTY(&mr->mr_molist))
+               mrt_free(mr);
+}
+
+struct multicast_route *
+mrt_find6(struct in6_addr *in6)
+{
+       struct multicast_route   key;
+
+       memset(&key, 0, sizeof(key));
+       key.mr_af = AF_INET6;
+       key.mr_group.v6 = *in6;
+       return RB_FIND(mrtree, &mrtree, &key);
+}
+
+struct multicast_route *
+mrt_insert6(enum mr_version mv, struct intf_data *id,
+    struct in6_addr *origin, struct in6_addr *group)
+{
+       struct multicast_route  *mr, *mrn;
+       union uaddr              uorigin;
+
+       /* Sanity check: only use multicast groups. */
+       if (!IN6_IS_ADDR_MULTICAST(group)) {
+               log_debug("%s(%s, %s): not multicast group",
+                   __func__, id->id_name, addr6tostr(group));
+               return NULL;
+       }
+
+       /* Try to find it, if it exists just add the new origin. */
+       mr = mrt_find6(group);
+       if (mr != NULL)
+               goto add_origin;
+
+       /* Otherwise create one and insert. */
+       mr = mrt_new();
+       if (mr == NULL)
+               return NULL;
+
+       mr->mr_af = AF_INET6;
+       mr->mr_group.v6 = *group;
+       mrn = RB_INSERT(mrtree, &mrtree, mr);
+       if (mrn != NULL) {
+               mrt_free(mr);
+               mr = mrn;
+       }
+
+ add_origin:
+       /*
+        * Always use the lowest version immediately, otherwise wait the
+        * query timeout before switching. See mrt_vtimer() for more
+        * details.
+        */
+       if (mr->mr_version > mv)
+               mr->mr_version = mv;
+       if (mr->mr_lowestversion > mv)
+               mr->mr_lowestversion = mv;
+
+       uorigin.v6 = *origin;
+       mrt_addorigin(mr, id, &uorigin);
+
+       mrt_nextstate(mr);
+
+       return mr;
+}
+
+void
+mrt_remove6(struct intf_data *id, struct in6_addr *origin,
+    struct in6_addr *group)
+{
+       struct multicast_route  *mr;
+       union uaddr              uorigin;
+
+       mr = mrt_find6(group);
+       if (mr == NULL)
+               return;
+
+       uorigin.v6 = *origin;
+       mrt_delorigin(mr, id, &uorigin);
+       mrt_nextstate(mr);
+       if (LIST_EMPTY(&mr->mr_molist))
+               mrt_free(mr);
+}
+
+void
+mrt_nextstate(struct multicast_route *mr)
+{
+       struct sockaddr_storage         ss;
+
+       if (mr->mr_upstream == NULL) {
+               log_debug("%s: no upstream interface", __func__);
+               return;
+       }
+
+       ss.ss_family = mr->mr_af;
+       switch (ss.ss_family) {
+       case AF_INET:
+               sstosin(&ss)->sin_addr = mr->mr_group.v4;
+               break;
+       case AF_INET6:
+               sstosin6(&ss)->sin6_addr = mr->mr_group.v6;
+               break;
+       default:
+               fatalx("%s: unknown family %d",
+                   __func__, ss.ss_family);
+       }
+
+       switch (mr->mr_state) {
+       case MS_NOTJOINED:
+               /* Don't join if there is no interest. */
+               if (LIST_EMPTY(&mr->mr_molist))
+                       return;
+
+               mcast_join(mr->mr_upstream, &ss);
+               mr->mr_state = MS_JOINED;
+               break;
+
+       case MS_JOINED:
+               /* Don't leave if there is still peers. */
+               if (!LIST_EMPTY(&mr->mr_molist))
+                       return;
+
+               mcast_leave(mr->mr_upstream, &ss);
+               mr->mr_state = MS_NOTJOINED;
+               break;
+
+       default:
+               log_debug("%s: invalid state %d",
+                   __func__, mr->mr_state);
+               break;
+       }
+}
+
+RB_GENERATE(mrtree, multicast_route, mr_entry, mrcmp);
+
+int
+mrcmp(struct multicast_route *mr1, struct multicast_route *mr2)
+{
+       size_t                   addrsize;
+
+       if (mr1->mr_af > mr2->mr_af)
+               return 1;
+       else if (mr1->mr_af < mr2->mr_af)
+               return -1;
+
+       addrsize = (mr1->mr_af == AF_INET) ?
+           sizeof(mr1->mr_group.v4) : sizeof(mr1->mr_group.v6);
+
+       return memcmp(&mr1->mr_group, &mr2->mr_group, addrsize);
+}
diff --git parse.y parse.y
new file mode 100644
index 0000000..ca6865c
--- /dev/null
+++ parse.y
@@ -0,0 +1,720 @@
+/*     $OpenBSD:$      */
+
+/*
+ * Copyright (c) 2017 Rafael Zalamena <rzalam...@openbsd.org>
+ * Copyright (c) 2015 Renato Westphal <ren...@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <no...@openbsd.org>
+ * Copyright (c) 2004 Ryan McBride <mcbr...@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henn...@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl.  All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+%{
+#include <arpa/inet.h>
+
+#include <sys/limits.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "mcast-proxy.h"
+
+struct file {
+       TAILQ_ENTRY(file)        entry;
+       FILE                    *stream;
+       char                    *name;
+       int                      lineno;
+       int                      errors;
+};
+TAILQ_HEAD(files, file);
+
+struct sym {
+       TAILQ_ENTRY(sym)         entry;
+       int                      used;
+       int                      persist;
+       char                    *nam;
+       char                    *val;
+};
+TAILQ_HEAD(symhead, sym);
+
+typedef struct {
+       union {
+               int64_t                  number;
+               char                    *string;
+       } v;
+       int lineno;
+} YYSTYPE;
+
+#define MAXPUSHBACK    128
+
+static int              yyerror(const char *, ...)
+    __attribute__((__format__ (printf, 1, 2)))
+    __attribute__((__nonnull__ (1)));
+static int              kw_cmp(const void *, const void *);
+static int              lookup(char *);
+static int              lgetc(int);
+static int              lungetc(int);
+static int              findeol(void);
+static int              yylex(void);
+static int              check_file_secrecy(int, const char *);
+static struct file     *pushfile(const char *, int);
+static int              popfile(void);
+static int              yyparse(void);
+static int              symset(const char *, const char *, int);
+static char            *symget(const char *);
+
+static struct file             *file, *topfile;
+static struct files             files = TAILQ_HEAD_INITIALIZER(files);
+static struct symhead           symhead = TAILQ_HEAD_INITIALIZER(symhead);
+static int                      errors;
+
+static unsigned char           *parsebuf;
+static int                      parseindex;
+static unsigned char            pushback_buffer[MAXPUSHBACK];
+static int                      pushback_index;
+
+struct intf_data       *cid;
+
+%}
+
+%token IPV4 IPV6 INTERFACE DISABLE DOWNSTREAM SOURCE UPSTREAM THRESHOLD
+%token INCLUDE YES NO
+%token ERROR
+%token <v.string>      STRING
+%token <v.number>      NUMBER
+%type  <v.number>      yesno
+%type  <v.string>      string
+
+%%
+
+grammar        : /* empty */
+       | grammar '\n'
+       | grammar conf_opt '\n'
+       | grammar include '\n'
+       | grammar varset '\n'
+       | grammar error '\n' { file->errors++; }
+       ;
+
+conf_opt : INTERFACE STRING {
+               cid = intf_lookupbyname($2);
+               if (cid == NULL) {
+                       cid = id_new();
+                       if (cid == NULL)
+                               fatal("%s:%d: calloc",
+                                   file->name, yylval.lineno);
+                       if (strlcpy(cid->id_name, $2,
+                           sizeof(cid->id_name)) >= sizeof(cid->id_name))
+                               fatalx("%s:%d: interface name too long",
+                                   file->name, yylval.lineno);
+               }
+
+               cid->id_mv4 = ic.ic_ipv4;
+               cid->id_mv6 = ic.ic_ipv6;
+       } intf_block
+       | global_ip
+       ;
+
+global_ip : IPV4 yesno { ic.ic_ipv4 = $2; }
+         | IPV6 yesno { ic.ic_ipv6 = $2; }
+         ;
+
+intf_block : '{' optnl intf_opts '}'
+          | '{' optnl '}'
+          ;
+
+intf_opts : intf_opt nl intf_opts
+         | intf_opt optnl
+         ;
+
+intf_opt : THRESHOLD NUMBER {
+               if ($2 < 1 || $2 > 255)
+                       fatalx("%s:%d: invalid threshold value: %llu",
+                           file->name, yylval.lineno, $2);
+
+               cid->id_ttl = $2;
+        }
+        | SOURCE STRING {
+               struct intf_addr        *ia;
+               char                    *prefixp;
+               const char              *errp;
+
+               prefixp = strchr($2, '/');
+               if (prefixp == NULL)
+                       fatalx("%s:%d: failed to find prefix",
+                           file->name, yylval.lineno);
+
+               *prefixp = 0;
+               prefixp++;
+               if (*prefixp == 0)
+                       fatalx("%s:%d: empty prefix",
+                           file->name, yylval.lineno);
+
+               ia = calloc(1, sizeof(*ia));
+               if (ia == NULL)
+                       fatal("%s:%d: calloc",
+                           file->name, yylval.lineno);
+
+               if (inet_pton(AF_INET, $2, &ia->ia_addr) != 1) {
+                       if (inet_pton(AF_INET6, $2, &ia->ia_addr) != 1) {
+                               fatalx("%s:%d: invalid address '%s'",
+                                   file->name, yylval.lineno, $2);
+                       } else
+                               ia->ia_af = AF_INET6;
+               } else
+                       ia->ia_af = AF_INET;
+
+               ia->ia_prefixlen = strtonum(prefixp, 0, 128, &errp);
+               if (errp != NULL)
+                       fatalx("%s:%d: invalid prefix length: %s",
+                           file->name, yylval.lineno, errp);
+               if (ia->ia_af == AF_INET && ia->ia_prefixlen > 32)
+                       fatalx("%s:%d: invalid prefix length",
+                           file->name, yylval.lineno);
+               else if (ia->ia_af == AF_INET6 && ia->ia_prefixlen > 128)
+                       fatalx("%s:%d: invalid prefix length",
+                           file->name, yylval.lineno);
+
+               SLIST_INSERT_HEAD(&cid->id_altnetlist, ia, ia_entry);
+       }
+       | UPSTREAM {
+               if (upstreamif != NULL)
+                       fatalx("%s:%d: it is not possible to have "
+                           "multiple upstream interfaces.",
+                           file->name, yylval.lineno);
+
+               upstreamif = cid;
+               cid->id_dir = IDIR_UPSTREAM;
+       }
+       | DOWNSTREAM { cid->id_dir = IDIR_DOWNSTREAM; }
+       | DISABLE { cid->id_dir = IDIR_DISABLE; }
+       | IPV4 yesno { cid->id_mv4 = $2; }
+       | IPV6 yesno { cid->id_mv6 = $2; }
+       ;
+
+include : INCLUDE STRING {
+               struct file     *nfile;
+
+               if ((nfile = pushfile($2, 1)) == NULL) {
+                       yyerror("failed to include file %s", $2);
+                       free($2);
+                       YYERROR;
+               }
+               free($2);
+
+               file = nfile;
+               lungetc('\n');
+       }
+       ;
+
+varset : STRING '=' string {
+               const char *s = $1;
+               while (*s++) {
+                       if (isspace((unsigned char)*s)) {
+                               yyerror("macro name cannot contain "
+                                   "whitespace");
+                               YYERROR;
+                       }
+               }
+               if (symset($1, $3, 0) == -1)
+                       fatal("cannot store variable");
+               free($1);
+               free($3);
+       }
+       ;
+
+string : string STRING {
+               if (asprintf(&$$, "%s %s", $1, $2) == -1) {
+                       free($1);
+                       free($2);
+                       yyerror("string: asprintf");
+                       YYERROR;
+               }
+               free($1);
+               free($2);
+       }
+       | STRING
+       ;
+
+optnl  : '\n' optnl
+       |
+       ;
+
+nl     : '\n' optnl
+       ;
+
+yesno  : YES   { $$ = 1; }
+       | NO    { $$ = 0; }
+       ;
+
+%%
+
+struct keywords {
+       const char      *k_name;
+       int              k_val;
+};
+
+static int
+yyerror(const char *fmt, ...)
+{
+       va_list          ap;
+       char            *msg;
+
+       file->errors++;
+       va_start(ap, fmt);
+       if (vasprintf(&msg, fmt, ap) == -1)
+               fatalx("yyerror vasprintf");
+       va_end(ap);
+       logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
+       free(msg);
+       return (0);
+}
+
+static int
+kw_cmp(const void *k, const void *e)
+{
+       return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+static int
+lookup(char *s)
+{
+       /* this has to be sorted always */
+       static const struct keywords keywords[] = {
+               {"disabled",                    DISABLE},
+               {"downstream",                  DOWNSTREAM},
+               {"include",                     INCLUDE},
+               {"interface",                   INTERFACE},
+               {"ipv4",                        IPV4},
+               {"ipv6",                        IPV6},
+               {"no",                          NO},
+               {"source",                      SOURCE},
+               {"threshold",                   THRESHOLD},
+               {"upstream",                    UPSTREAM},
+               {"yes",                         YES},
+       };
+       const struct keywords   *p;
+
+       p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+           sizeof(keywords[0]), kw_cmp);
+
+       if (p)
+               return (p->k_val);
+       else
+               return (STRING);
+}
+
+static int
+lgetc(int quotec)
+{
+       int             c, next;
+
+       if (parsebuf) {
+               /* Read character from the parsebuffer instead of input. */
+               if (parseindex >= 0) {
+                       c = parsebuf[parseindex++];
+                       if (c != '\0')
+                               return (c);
+                       parsebuf = NULL;
+               } else
+                       parseindex++;
+       }
+
+       if (pushback_index)
+               return (pushback_buffer[--pushback_index]);
+
+       if (quotec) {
+               if ((c = getc(file->stream)) == EOF) {
+                       yyerror("reached end of file while parsing "
+                           "quoted string");
+                       if (file == topfile || popfile() == EOF)
+                               return (EOF);
+                       return (quotec);
+               }
+               return (c);
+       }
+
+       while ((c = getc(file->stream)) == '\\') {
+               next = getc(file->stream);
+               if (next != '\n') {
+                       c = next;
+                       break;
+               }
+               yylval.lineno = file->lineno;
+               file->lineno++;
+       }
+
+       while (c == EOF) {
+               if (file == topfile || popfile() == EOF)
+                       return (EOF);
+               c = getc(file->stream);
+       }
+       return (c);
+}
+
+static int
+lungetc(int c)
+{
+       if (c == EOF)
+               return (EOF);
+       if (parsebuf) {
+               parseindex--;
+               if (parseindex >= 0)
+                       return (c);
+       }
+       if (pushback_index < MAXPUSHBACK-1)
+               return (pushback_buffer[pushback_index++] = c);
+       else
+               return (EOF);
+}
+
+static int
+findeol(void)
+{
+       int     c;
+
+       parsebuf = NULL;
+
+       /* skip to either EOF or the first real EOL */
+       while (1) {
+               if (pushback_index)
+                       c = pushback_buffer[--pushback_index];
+               else
+                       c = lgetc(0);
+               if (c == '\n') {
+                       file->lineno++;
+                       break;
+               }
+               if (c == EOF)
+                       break;
+       }
+       return (ERROR);
+}
+
+static int
+yylex(void)
+{
+       unsigned char    buf[8096];
+       unsigned char   *p, *val;
+       int              quotec, next, c;
+       int              token;
+
+ top:
+       p = buf;
+       while ((c = lgetc(0)) == ' ' || c == '\t')
+               ; /* nothing */
+
+       yylval.lineno = file->lineno;
+       if (c == '#')
+               while ((c = lgetc(0)) != '\n' && c != EOF)
+                       ; /* nothing */
+       if (c == '$' && parsebuf == NULL) {
+               while (1) {
+                       if ((c = lgetc(0)) == EOF)
+                               return (0);
+
+                       if (p + 1 >= buf + sizeof(buf) - 1) {
+                               yyerror("string too long");
+                               return (findeol());
+                       }
+                       if (isalnum(c) || c == '_') {
+                               *p++ = c;
+                               continue;
+                       }
+                       *p = '\0';
+                       lungetc(c);
+                       break;
+               }
+               val = symget(buf);
+               if (val == NULL) {
+                       yyerror("macro '%s' not defined", buf);
+                       return (findeol());
+               }
+               parsebuf = val;
+               parseindex = 0;
+               goto top;
+       }
+
+       switch (c) {
+       case '\'':
+       case '"':
+               quotec = c;
+               while (1) {
+                       if ((c = lgetc(quotec)) == EOF)
+                               return (0);
+                       if (c == '\n') {
+                               file->lineno++;
+                               continue;
+                       } else if (c == '\\') {
+                               if ((next = lgetc(quotec)) == EOF)
+                                       return (0);
+                               if (next == quotec || c == ' ' || c == '\t')
+                                       c = next;
+                               else if (next == '\n') {
+                                       file->lineno++;
+                                       continue;
+                               } else
+                                       lungetc(next);
+                       } else if (c == quotec) {
+                               *p = '\0';
+                               break;
+                       } else if (c == '\0') {
+                               yyerror("syntax error");
+                               return (findeol());
+                       }
+                       if (p + 1 >= buf + sizeof(buf) - 1) {
+                               yyerror("string too long");
+                               return (findeol());
+                       }
+                       *p++ = c;
+               }
+               yylval.v.string = strdup(buf);
+               if (yylval.v.string == NULL)
+                       err(1, "yylex: strdup");
+               return (STRING);
+       }
+
+#define allowed_to_end_number(x) \
+       (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+       if (c == '-' || isdigit(c)) {
+               do {
+                       *p++ = c;
+                       if ((unsigned)(p-buf) >= sizeof(buf)) {
+                               yyerror("string too long");
+                               return (findeol());
+                       }
+               } while ((c = lgetc(0)) != EOF && isdigit(c));
+               lungetc(c);
+               if (p == buf + 1 && buf[0] == '-')
+                       goto nodigits;
+               if (c == EOF || allowed_to_end_number(c)) {
+                       const char *errstr = NULL;
+
+                       *p = '\0';
+                       yylval.v.number = strtonum(buf, LLONG_MIN,
+                           LLONG_MAX, &errstr);
+                       if (errstr) {
+                               yyerror("\"%s\" invalid number: %s",
+                                   buf, errstr);
+                               return (findeol());
+                       }
+                       return (NUMBER);
+               } else {
+nodigits:
+                       while (p > buf + 1)
+                               lungetc(*--p);
+                       c = *--p;
+                       if (c == '-')
+                               return (c);
+               }
+       }
+
+#define allowed_in_string(x) \
+       (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+       x != '{' && x != '}' && \
+       x != '!' && x != '=' && x != '#' && \
+       x != ','))
+
+       if (isalnum(c) || c == ':' || c == '_') {
+               do {
+                       *p++ = c;
+                       if ((unsigned)(p-buf) >= sizeof(buf)) {
+                               yyerror("string too long");
+                               return (findeol());
+                       }
+               } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
+               lungetc(c);
+               *p = '\0';
+               if ((token = lookup(buf)) == STRING)
+                       if ((yylval.v.string = strdup(buf)) == NULL)
+                               err(1, "yylex: strdup");
+               return (token);
+       }
+       if (c == '\n') {
+               yylval.lineno = file->lineno;
+               file->lineno++;
+       }
+       if (c == EOF)
+               return (0);
+       return (c);
+}
+
+static int
+check_file_secrecy(int fd, const char *fname)
+{
+       struct stat     st;
+
+       if (fstat(fd, &st)) {
+               log_warn("cannot stat %s", fname);
+               return (-1);
+       }
+       if (st.st_uid != 0 && st.st_uid != getuid()) {
+               log_warnx("%s: owner not root or current user", fname);
+               return (-1);
+       }
+       if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
+               log_warnx("%s: group writable or world read/writable", fname);
+               return (-1);
+       }
+       return (0);
+}
+
+static struct file *
+pushfile(const char *name, int secret)
+{
+       struct file     *nfile;
+
+       if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
+               log_warn("calloc");
+               return (NULL);
+       }
+       if ((nfile->name = strdup(name)) == NULL) {
+               log_warn("strdup");
+               free(nfile);
+               return (NULL);
+       }
+       if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+               log_warn("%s", nfile->name);
+               free(nfile->name);
+               free(nfile);
+               return (NULL);
+       } else if (secret &&
+           check_file_secrecy(fileno(nfile->stream), nfile->name)) {
+               fclose(nfile->stream);
+               free(nfile->name);
+               free(nfile);
+               return (NULL);
+       }
+       nfile->lineno = 1;
+       TAILQ_INSERT_TAIL(&files, nfile, entry);
+       return (nfile);
+}
+
+static int
+popfile(void)
+{
+       struct file     *prev;
+
+       if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
+               prev->errors += file->errors;
+
+       TAILQ_REMOVE(&files, file, entry);
+       fclose(file->stream);
+       free(file->name);
+       free(file);
+       file = prev;
+       return (file ? 0 : EOF);
+}
+
+static int
+symset(const char *nam, const char *val, int persist)
+{
+       struct sym      *sym;
+
+       TAILQ_FOREACH(sym, &symhead, entry) {
+               if (strcmp(nam, sym->nam) == 0)
+                       break;
+       }
+
+       if (sym != NULL) {
+               if (sym->persist == 1)
+                       return (0);
+               else {
+                       free(sym->nam);
+                       free(sym->val);
+                       TAILQ_REMOVE(&symhead, sym, entry);
+                       free(sym);
+               }
+       }
+       if ((sym = calloc(1, sizeof(*sym))) == NULL)
+               return (-1);
+
+       sym->nam = strdup(nam);
+       if (sym->nam == NULL) {
+               free(sym);
+               return (-1);
+       }
+       sym->val = strdup(val);
+       if (sym->val == NULL) {
+               free(sym->nam);
+               free(sym);
+               return (-1);
+       }
+       sym->used = 0;
+       sym->persist = persist;
+       TAILQ_INSERT_TAIL(&symhead, sym, entry);
+       return (0);
+}
+
+int
+cmdline_symset(const char *s)
+{
+       char    *sym, *val;
+       int     ret;
+       size_t  len;
+
+       if ((val = strrchr(s, '=')) == NULL)
+               return (-1);
+
+       len = strlen(s) - strlen(val) + 1;
+       if ((sym = malloc(len)) == NULL)
+               errx(1, "cmdline_symset: malloc");
+
+       strlcpy(sym, s, len);
+
+       ret = symset(sym, val + 1, 1);
+       free(sym);
+
+       return (ret);
+}
+
+static char *
+symget(const char *nam)
+{
+       struct sym      *sym;
+
+       TAILQ_FOREACH(sym, &symhead, entry) {
+               if (strcmp(nam, sym->nam) == 0) {
+                       sym->used = 1;
+                       return (sym->val);
+               }
+       }
+       return (NULL);
+}
+
+int
+parse_config(const char *filename)
+{
+       if ((file = pushfile(filename, 0)) == NULL)
+               return -1;
+
+       topfile = file;
+
+       yyparse();
+       errors = file->errors;
+       popfile();
+       if (errors)
+               return -1;
+
+       return 0;
+}
diff --git util.c util.c
new file mode 100644
index 0000000..4594f8a
--- /dev/null
+++ util.c
@@ -0,0 +1,474 @@
+/*     $OpenBSD:$      */
+
+/*
+ * Copyright (c) 2017 Rafael Zalamena <rzalam...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <arpa/inet.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "mcast-proxy.h"
+
+const char *
+addrtostr(struct sockaddr_storage *ss)
+{
+       struct sockaddr_in      *sin;
+       struct sockaddr_in6     *sin6;
+       static char              buf[4][128];
+       static unsigned int      bufpos = 0;
+
+       bufpos = (bufpos + 1) % 4;
+
+       switch (ss->ss_family) {
+       case AF_INET:
+               sin = sstosin(ss);
+               inet_ntop(AF_INET, &sin->sin_addr, buf[bufpos],
+                   sizeof(buf[bufpos]));
+               return buf[bufpos];
+       case AF_INET6:
+               sin6 = sstosin6(ss);
+               buf[bufpos][0] = '[';
+               inet_ntop(AF_INET6, &sin6->sin6_addr, &buf[bufpos][1],
+                   sizeof(buf[bufpos]));
+               strlcat(buf[bufpos], "]", sizeof(buf[bufpos]));
+               return buf[bufpos];
+
+       default:
+               return "unknown";
+       }
+}
+
+const char *
+addr4tostr(struct in_addr *addr)
+{
+       struct sockaddr_storage          ss;
+
+       memset(&ss, 0, sizeof(ss));
+       ss.ss_family = AF_INET;
+       ss.ss_len = sizeof(struct sockaddr_in);
+       sstosin(&ss)->sin_addr = *addr;
+
+       return addrtostr(&ss);
+}
+
+const char *
+addr6tostr(struct in6_addr *addr)
+{
+       struct sockaddr_storage          ss;
+
+       memset(&ss, 0, sizeof(ss));
+       ss.ss_family = AF_INET6;
+       ss.ss_len = sizeof(struct sockaddr_in6);
+       memcpy(&sstosin6(&ss)->sin6_addr, addr, sizeof(*addr));
+
+       return addrtostr(&ss);
+}
+
+int
+id_matchaddr4(struct intf_data *id, uint32_t addr)
+{
+       struct intf_addr        *ia;
+       union uaddr              addrorg, addrtgt, naddr;
+
+       naddr.v4.s_addr = addr;
+
+       /* Check for address in interface address list. */
+       SLIST_FOREACH(ia, &id->id_ialist, ia_entry) {
+               if (ia->ia_af != AF_INET)
+                       continue;
+
+               applymask(AF_INET, &addrtgt, &naddr, ia->ia_prefixlen);
+               applymask(AF_INET, &addrorg, &ia->ia_addr, ia->ia_prefixlen);
+               if (memcmp(&addrorg, &addrtgt, sizeof(addrorg.v4)) == 0)
+                       return 1;
+       }
+
+       /* Check for address in the subnet address list. */
+       SLIST_FOREACH(ia, &id->id_altnetlist, ia_entry) {
+               if (ia->ia_af != AF_INET)
+                       continue;
+
+               applymask(AF_INET, &addrtgt, &naddr, ia->ia_prefixlen);
+               applymask(AF_INET, &addrorg, &ia->ia_addr, ia->ia_prefixlen);
+               if (memcmp(&addrorg, &addrtgt, sizeof(addrorg.v4)) == 0)
+                       return 1;
+       }
+
+       return 0;
+}
+
+int
+id_matchaddr6(struct intf_data *id, struct in6_addr *addr)
+{
+       struct intf_addr        *ia;
+       union uaddr              addrorg, addrtgt, naddr;
+
+       naddr.v6 = *addr;
+
+       /* Check for address in interface address list. */
+       SLIST_FOREACH(ia, &id->id_ialist, ia_entry) {
+               if (ia->ia_af != AF_INET6)
+                       continue;
+
+               applymask(AF_INET6, &addrtgt, &naddr, ia->ia_prefixlen);
+               applymask(AF_INET6, &addrorg, &ia->ia_addr, ia->ia_prefixlen);
+               if (memcmp(&addrorg, &addrtgt, sizeof(addrorg.v6)) == 0)
+                       return 1;
+       }
+
+       /* Check for address in the subnet address list. */
+       SLIST_FOREACH(ia, &id->id_altnetlist, ia_entry) {
+               if (ia->ia_af != AF_INET6)
+                       continue;
+
+               applymask(AF_INET6, &addrtgt, &naddr, ia->ia_prefixlen);
+               applymask(AF_INET6, &addrorg, &ia->ia_addr, ia->ia_prefixlen);
+               if (memcmp(&addrorg, &addrtgt, sizeof(addrorg.v6)) == 0)
+                       return 1;
+       }
+
+       return 0;
+}
+
+struct intf_data *
+intf_lookupbyname(const char *ifname)
+{
+       struct intf_data        *id;
+
+       SLIST_FOREACH(id, &iflist, id_entry) {
+               if (strcmp(id->id_name, ifname) == 0)
+                       return id;
+       }
+
+       return NULL;
+}
+
+struct intf_data *
+intf_lookupbyindex(unsigned short index)
+{
+       struct intf_data        *id;
+
+       SLIST_FOREACH(id, &iflist, id_entry) {
+               if (id->id_index == index)
+                       return id;
+       }
+
+       return NULL;
+}
+
+struct intf_data *
+intf_lookupbyaddr4(uint32_t addr)
+{
+       struct intf_data        *id;
+
+       SLIST_FOREACH(id, &iflist, id_entry) {
+               if (id_matchaddr4(id, addr))
+                       return id;
+       }
+
+       return NULL;
+}
+
+struct intf_data *
+intf_lookupbyaddr6(struct in6_addr *addr)
+{
+       struct intf_data        *id;
+
+       SLIST_FOREACH(id, &iflist, id_entry) {
+               if (id_matchaddr6(id, addr))
+                       return id;
+       }
+
+       return NULL;
+}
+
+struct intf_addr *
+intf_primaryv4(struct intf_data *id)
+{
+       struct intf_addr        *ia;
+
+       SLIST_FOREACH(ia, &id->id_ialist, ia_entry) {
+               if (ia->ia_af != AF_INET)
+                       continue;
+
+               return ia;
+       }
+
+       return NULL;
+}
+
+struct intf_addr *
+intf_ipv6linklayer(struct intf_data *id)
+{
+       struct intf_addr        *ia;
+
+       SLIST_FOREACH(ia, &id->id_ialist, ia_entry) {
+               if (ia->ia_af != AF_INET6)
+                       continue;
+               if (!IN6_IS_ADDR_LINKLOCAL(&ia->ia_addr.v6))
+                       continue;
+
+               return ia;
+       }
+
+       return NULL;
+}
+
+void
+ia_inserttail(struct ialist *ial, struct intf_addr *ia)
+{
+       struct intf_addr        *ian;
+
+       SLIST_FOREACH(ian, ial, ia_entry) {
+               if (SLIST_NEXT(ian, ia_entry) == NULL)
+                       break;
+       }
+       if (ian != NULL)
+               SLIST_INSERT_AFTER(ian, ia, ia_entry);
+       else
+               SLIST_INSERT_HEAD(ial, ia, ia_entry);
+}
+
+struct intf_data *
+id_new(void)
+{
+       struct intf_data        *id;
+
+       id = calloc(1, sizeof(*id));
+       if (id == NULL) {
+               log_warn("%s: calloc", __func__);
+               return NULL;
+       }
+
+       /* Default minimum TTL threshold. */
+       id->id_ttl = 1;
+
+       id->id_index = (unsigned short)-1;
+       id->id_vindex = INVALID_VINDEX;
+       id->id_vindex6 = INVALID_VINDEX;
+       SLIST_INSERT_HEAD(&iflist, id, id_entry);
+
+       return id;
+}
+
+struct intf_data *
+id_insert(unsigned short index)
+{
+       struct intf_data        *id;
+
+       id = intf_lookupbyindex(index);
+       if (id != NULL)
+               return id;
+
+       id = id_new();
+       if (id == NULL)
+               return NULL;
+
+       id->id_index = index;
+
+       return id;
+}
+
+void
+id_free(struct intf_data *id)
+{
+       struct intf_addr        *ia;
+
+       if (id == NULL)
+               return;
+
+       while (!SLIST_EMPTY(&id->id_ialist)) {
+               ia = SLIST_FIRST(&id->id_ialist);
+               SLIST_REMOVE(&id->id_ialist, ia, intf_addr, ia_entry);
+               free(ia);
+       }
+       while (!SLIST_EMPTY(&id->id_altnetlist)) {
+               ia = SLIST_FIRST(&id->id_altnetlist);
+               SLIST_REMOVE(&id->id_altnetlist, ia, intf_addr, ia_entry);
+               free(ia);
+       }
+
+       SLIST_REMOVE(&iflist, id, intf_data, id_entry);
+       free(id);
+}
+
+uint8_t
+mask2prefixlen(in_addr_t ina)
+{
+       if (ina == 0)
+               return (0);
+       else
+               return (33 - ffs(ntohl(ina)));
+}
+
+uint8_t
+mask2prefixlen6(struct sockaddr_in6 *sa_in6)
+{
+       uint8_t l = 0, *ap, *ep;
+
+       /*
+        * sin6_len is the size of the sockaddr so substract the offset of
+        * the possibly truncated sin6_addr struct.
+        */
+       ap = (uint8_t *)&sa_in6->sin6_addr;
+       ep = (uint8_t *)sa_in6 + sa_in6->sin6_len;
+       for (; ap < ep; ap++) {
+               /* this "beauty" is adopted from sbin/route/show.c ... */
+               switch (*ap) {
+               case 0xff:
+                       l += 8;
+                       break;
+               case 0xfe:
+                       l += 7;
+                       return (l);
+               case 0xfc:
+                       l += 6;
+                       return (l);
+               case 0xf8:
+                       l += 5;
+                       return (l);
+               case 0xf0:
+                       l += 4;
+                       return (l);
+               case 0xe0:
+                       l += 3;
+                       return (l);
+               case 0xc0:
+                       l += 2;
+                       return (l);
+               case 0x80:
+                       l += 1;
+                       return (l);
+               case 0x00:
+                       return (l);
+               default:
+                       fatalx("%s: non contiguous inet6 netmask", __func__);
+               }
+       }
+
+       return (l);
+}
+
+in_addr_t
+prefixlen2mask(uint8_t prefixlen)
+{
+       if (prefixlen == 0)
+               return (0);
+
+       return (htonl(0xffffffff << (32 - prefixlen)));
+}
+
+void
+applymask(int af, union uaddr *dest, const union uaddr *src,
+    int prefixlen)
+{
+       struct in6_addr mask;
+       int             i;
+
+       switch (af) {
+       case AF_INET:
+               dest->v4.s_addr = src->v4.s_addr & prefixlen2mask(prefixlen);
+               break;
+       case AF_INET6:
+               memset(&mask, 0, sizeof(mask));
+               for (i = 0; i < prefixlen / 8; i++)
+                       mask.s6_addr[i] = 0xff;
+               i = prefixlen % 8;
+               if (i)
+                       mask.s6_addr[prefixlen / 8] = 0xff00 >> i;
+
+               for (i = 0; i < 16; i++)
+                       dest->v6.s6_addr[i] = src->v6.s6_addr[i] &
+                           mask.s6_addr[i];
+               break;
+       default:
+               fatalx("%s: unknown address family", __func__);
+       }
+}
+
+/* Packet assembly code, originally contributed by Archie Cobbs. */
+
+/*
+ * Copyright (c) 1995, 1996, 1999 The Internet Software Consortium.
+ * 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 Internet Software Consortium 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 INTERNET SOFTWARE CONSORTIUM 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 INTERNET SOFTWARE CONSORTIUM 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.
+ *
+ * This software has been written for the Internet Software Consortium
+ * by Ted Lemon <mel...@fugue.com> in cooperation with Vixie
+ * Enterprises.  To learn more about the Internet Software Consortium,
+ * see ``http://www.vix.com/isc''.  To learn more about Vixie
+ * Enterprises, see ``http://www.vix.com''.
+ */
+
+uint16_t
+checksum(uint8_t *buf, uint16_t nbytes, uint32_t sum)
+{
+       unsigned int i;
+
+       /* Checksum all the pairs of bytes first... */
+       for (i = 0; i < (nbytes & ~1U); i += 2) {
+               sum += (u_int16_t)ntohs(*((u_int16_t *)(buf + i)));
+               if (sum > 0xFFFF)
+                       sum -= 0xFFFF;
+       }
+
+       /*
+        * If there's a single byte left over, checksum it, too.
+        * Network byte order is big-endian, so the remaining byte is
+        * the high byte.
+        */
+       if (i < nbytes) {
+               sum += buf[i] << 8;
+               if (sum > 0xFFFF)
+                       sum -= 0xFFFF;
+       }
+
+       return sum;
+}
+
+uint16_t
+wrapsum(uint16_t sum)
+{
+       sum = ~sum & 0xFFFF;
+       return htons(sum);
+}

Reply via email to