Hi tech@,

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

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

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

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

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

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

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

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

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

Cheers,

Paul 'WEiRD' de Weerd

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

diff -ur a/mcast-proxy.c b/mcast-proxy.c
--- a/mcast-proxy.c     Sat May 27 22:20:02 2017
+++ b/mcast-proxy.c     Sat May 27 18:25:53 2017
@@ -475,24 +475,6 @@
                return 0;
        }
 
-       /* IP header validations. */
-       if (ip->ip_v != IPVERSION) {
-               log_debug("%s: wrong IP version", __func__);
-               return 0;
-       }
-       hlen = ip->ip_hl << 2;
-       if (hlen < sizeof(*ip)) {
-               log_debug("%s: wrong IP header length", __func__);
-               return 0;
-       }
-       if ((ip->ip_off & IP_OFFMASK) != 0) {
-               log_debug("%s: fragmented packet", __func__);
-               return 0;
-       }
-       if (ip->ip_ttl == 0) {
-               log_debug("%s: invalid TTL", __func__);
-               return 0;
-       }
        if (ip->ip_src.s_addr == INADDR_ANY ||
            ip->ip_dst.s_addr == INADDR_ANY) {
                log_debug("%s: invalid packet addresses", __func__);
@@ -525,6 +507,25 @@
                log_debug("%s: expected IGMP message, got %d",
                    __func__, ip->ip_p);
                return NULL;
+       }
+       /* IP header validations. */
+       if (ip->ip_v != IPVERSION) {
+               log_debug("%s: wrong IP version", __func__);
+               return 0;
+       }
+       hlen = ip->ip_hl << 2;
+       if (hlen < sizeof(*ip)) {
+               log_debug("%s: wrong IP header length", __func__);
+               return 0;
+       }
+/* Disable check to see if it makes a difference */
+/*     if ((ip->ip_off & IP_OFFMASK) != 0) {
+               log_debug("%s: fragmented packet", __func__);
+               return 0;
+       }*/
+       if (ip->ip_ttl == 0) {
+               log_debug("%s: invalid TTL", __func__);
+               return 0;
        }
 
        hlen = ip->ip_hl << 2;
diff -ur a/mrt.c b/mrt.c
--- a/mrt.c     Sat May 27 22:20:02 2017
+++ b/mrt.c     Sat May 27 22:10:01 2017
@@ -514,7 +514,7 @@
 {
        struct sockaddr_storage         ss;
 
-       if (mr->mr_upstream == NULL) {
+       if (upstreamif == NULL) {
                log_debug("%s: no upstream interface", __func__);
                return;
        }
@@ -538,7 +538,7 @@
                if (LIST_EMPTY(&mr->mr_molist))
                        return;
 
-               mcast_join(mr->mr_upstream, &ss);
+               mcast_join(upstreamif, &ss);
                mr->mr_state = MS_JOINED;
                break;
 
@@ -547,7 +547,7 @@
                if (!LIST_EMPTY(&mr->mr_molist))
                        return;
 
-               mcast_leave(mr->mr_upstream, &ss);
+               mcast_leave(upstreamif, &ss);
                mr->mr_state = MS_NOTJOINED;
                break;
 


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

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

Reply via email to