mcast-proxy daemon

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

mcast-proxy daemon

Rafael Zalamena-2
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 <[hidden email]>
+ *
+ * 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 <[hidden email]>
+ *
+ * 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 <[hidden email]>
+ *
+ * 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 <[hidden email]>
+.\"
+.\" 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 [hidden email]
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 <[hidden email]>
+ *
+ * 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 <[hidden email]>
+.\"
+.\" 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 [hidden email] .
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 <[hidden email]>
+ *
+ * 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 <[hidden email]>
+ *
+ * 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 <[hidden email]>
+ * Copyright (c) 2015 Renato Westphal <[hidden email]>
+ * Copyright (c) 2004, 2005 Esben Norby <[hidden email]>
+ * Copyright (c) 2004 Ryan McBride <[hidden email]>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <[hidden email]>
+ * 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 <[hidden email]>
+ *
+ * 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 <[hidden email]> 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 | Threaded
Open this post in threaded view
|

Re: mcast-proxy daemon

Paul de Weerd
Hi tech@,

I've been talking to Rafael about this piece of code over the last
week a bit, and have tried it on my home TV setup.  My ISP has a
multicast setup for watching television, with their set-top-box
connected to my TV.  Up until now, I was using net/igmpproxy on my
OpenBSD gateway to get the streams from ISP to STB.

At first, Rafael's code didn't work for me, but after some back and
forth with him we got things going with the below diff (to be applied
on top of Rafael's diff).  Obviously some things are still missing:

        - runs as user _dhcp
        - make install gives you /mcast-proxy
        - lacks an rc.d init script
        - no pledge(2) for the important part of the code

However, this is already an incredible jump forward in terms of
usability (sane defaults!) and stability, let alone safe coding
practices over igmpproxy: runs as root, doesn't drop privs, doesn't
chroot, etc.

Compare my old /etc/igmpproxy.conf with my new /etc/mcast-proxy.conf:

--- /etc/igmpproxy.conf ----------------------------------------------
quickleave
phyint vlan4 upstream ratelimit 0 threshold 1 altnet 10.0.0.0/8
phyint vlan10 downstream ratelimit 0 threshold 1
phyint lo0 disabled
phyint em0 disabled
phyint em1 disabled
phyint em2 disabled
phyint em3 disabled
phyint em4 disabled
phyint em5 disabled
phyint bridge0 disabled
phyint bridge1 disabled
phyint bridge20 disabled
phyint vlan20 disabled
phyint vlan21 disabled
----------------------------------------------------------------------

--- /etc/mcast-proxy.conf --------------------------------------------
interface vlan4 {
        upstream
        source 10.0.0.0/8
}

interface vlan10 {
        downstream
}
----------------------------------------------------------------------

It would be really awesome to have something like this in base.  Your
networking kit really isn't complete without a multicast proxying
solution!  (plus, it's nice to have something that exercises these
codepaths in base).

Cheers,

Paul 'WEiRD' de Weerd

PS: Please note that any mistakes in the below diff are mine; Rafael
guided me through writing this over e-mail.  Sending this out so
others can help test, as Rafael mentioned he'd be AFK for a few days.

diff -ur a/mcast-proxy.c b/mcast-proxy.c
--- a/mcast-proxy.c Sat May 27 22:20:02 2017
+++ b/mcast-proxy.c Sat May 27 18:25:53 2017
@@ -475,24 +475,6 @@
  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__);
@@ -525,6 +507,25 @@
  log_debug("%s: expected IGMP message, got %d",
     __func__, ip->ip_p);
  return NULL;
+ }
+ /* 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;
+ }
+/* Disable check to see if it makes a difference */
+/* 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;
  }
 
  hlen = ip->ip_hl << 2;
diff -ur a/mrt.c b/mrt.c
--- a/mrt.c Sat May 27 22:20:02 2017
+++ b/mrt.c Sat May 27 22:10:01 2017
@@ -514,7 +514,7 @@
 {
  struct sockaddr_storage ss;
 
- if (mr->mr_upstream == NULL) {
+ if (upstreamif == NULL) {
  log_debug("%s: no upstream interface", __func__);
  return;
  }
@@ -538,7 +538,7 @@
  if (LIST_EMPTY(&mr->mr_molist))
  return;
 
- mcast_join(mr->mr_upstream, &ss);
+ mcast_join(upstreamif, &ss);
  mr->mr_state = MS_JOINED;
  break;
 
@@ -547,7 +547,7 @@
  if (!LIST_EMPTY(&mr->mr_molist))
  return;
 
- mcast_leave(mr->mr_upstream, &ss);
+ mcast_leave(upstreamif, &ss);
  mr->mr_state = MS_NOTJOINED;
  break;
 


On Fri, May 19, 2017 at 06:25:01PM +0200, Rafael Zalamena wrote:
| 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 <[hidden email]>
| + *
| + * 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 <[hidden email]>
| + *
| + * 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 <[hidden email]>
| + *
| + * 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 <[hidden email]>
| +.\"
| +.\" 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 [hidden email]
| 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 <[hidden email]>
| + *
| + * 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 <[hidden email]>
| +.\"
| +.\" 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 [hidden email] .
| 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 <[hidden email]>
| + *
| + * 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 <[hidden email]>
| + *
| + * 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 <[hidden email]>
| + * Copyright (c) 2015 Renato Westphal <[hidden email]>
| + * Copyright (c) 2004, 2005 Esben Norby <[hidden email]>
| + * Copyright (c) 2004 Ryan McBride <[hidden email]>
| + * Copyright (c) 2002, 2003, 2004 Henning Brauer <[hidden email]>
| + * 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 <[hidden email]>
| + *
| + * 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 <[hidden email]> 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);
| +}
|

--
>++++++++[<++++++++++>-]<+++++++.>+++[<------>-]<.>+++[<+
+++++++++++>-]<.>++[<------------>-]<+.--------------.[-]
                 http://www.weirdnet.nl/                 

Reply | Threaded
Open this post in threaded view
|

Re: mcast-proxy daemon

Paul de Weerd
On Sat, May 27, 2017 at 11:05:13PM +0200, Paul de Weerd wrote:
| - lacks an rc.d init script

Here's a diff that adds an rc.d script 'mcastproxy' (other daemons
that have a '-' in their name also drop it in their rc.d script, so I
followed that example) I stuck it in /etc/rc around the startup of
other proxies, but there may be better places for it.

Cheers,

Paul 'WEiRD' de Weerd

--
>++++++++[<++++++++++>-]<+++++++.>+++[<------>-]<.>+++[<+
+++++++++++>-]<.>++[<------------>-]<+.--------------.[-]
                 http://www.weirdnet.nl/                 

Index: rc
===================================================================
RCS file: /cvs/src/etc/rc,v
retrieving revision 1.498
diff -u -p -r1.498 rc
--- rc 30 May 2017 12:04:26 -0000 1.498
+++ rc 3 Jun 2017 14:29:13 -0000
@@ -549,7 +549,7 @@ if ifconfig lo0 inet6 >/dev/null 2>&1; t
  fi
 fi
 
-start_daemon hostapd lpd smtpd slowcgi httpd ftpd
+start_daemon hostapd lpd smtpd slowcgi httpd ftpd mcastproxy
 start_daemon ftpproxy ftpproxy6 tftpd tftpproxy identd inetd rarpd bootparamd
 start_daemon rbootd mopd vmd spamd spamlogd sndiod
 echo '.'
Index: rc.conf
===================================================================
RCS file: /cvs/src/etc/rc.conf,v
retrieving revision 1.216
diff -u -p -r1.216 rc.conf
--- rc.conf 30 May 2017 12:04:26 -0000 1.216
+++ rc.conf 3 Jun 2017 14:27:48 -0000
@@ -38,6 +38,7 @@ ldattach_flags=NO # for normal use: "[op
 ldomd_flags=NO
 ldpd_flags=NO
 lpd_flags=NO # for normal use: "" (or "-l" for debugging)
+mcastproxy_flags=NO
 mopd_flags=NO
 mrouted_flags=NO # be sure to enable multicast below
 npppd_flags=NO
Index: rc.d/mcastproxy
===================================================================
RCS file: rc.d/mcastproxy
diff -N rc.d/mcastproxy
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ rc.d/mcastproxy 3 Jun 2017 14:26:53 -0000
@@ -0,0 +1,11 @@
+#!/bin/sh
+#
+# $OpenBSD$
+
+daemon="/usr/sbin/mcast-proxy"
+
+. /etc/rc.d/rc.subr
+
+rc_reload=NO
+
+rc_cmd $1