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 <[email protected]>
+ *
+ * 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 <[email protected]>
+ *
+ * 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 <[email protected]>
+ *
+ * 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 <[email protected]>
+.\"
+.\" 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 [email protected]
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 <[email protected]>
+ *
+ * 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 <[email protected]>
+.\"
+.\" 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 [email protected] .
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 <[email protected]>
+ *
+ * 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 <[email protected]>
+ *
+ * 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 <[email protected]>
+ * Copyright (c) 2015 Renato Westphal <[email protected]>
+ * Copyright (c) 2004, 2005 Esben Norby <[email protected]>
+ * Copyright (c) 2004 Ryan McBride <[email protected]>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <[email protected]>
+ * 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 <[email protected]>
+ *
+ * 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 <[email protected]> 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);
+}