/*
 * Copyright (C) 1993 AmiTCP/IP Group, <amitcp-group@hut.fi>
 *                    Helsinki University of Technology, Finland.
 *                    All rights reserved.
 * Copyright (C) 2005 Neil Cafferkey
 * Copyright (C) 2005-2026 The AROS Dev Team
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 *
 */

/*
 * Copyright (C) 1982, 1986, 1991 Regents of the University of California.
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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.
 *
 *	@(#)in.c	7.17 (Berkeley) 4/20/91
 */

#include <conf.h>

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/ioctl.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/synch.h>

#include <netinet/in_systm.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include "in_var.h"

#include <netinet/in_protos.h>
#include <net/rtsock_protos.h>

#ifdef ENABLE_MULTICAST
#include <net/if_sana.h>
#include <net/sana2request.h>
#include <devices/sana2.h>
#include <netinet/igmp.h>
#endif

#if INET
/*
 * Formulate an Internet address from network + host.
 */
struct in_addr
in_makeaddr(net, host)
u_long net, host;
{
    register struct in_ifaddr *ia;
    register u_long mask;
    union {
        u_long addr;
        struct in_addr in_addr;
    } __tmp;

    if(IN_CLASSA(net))
        mask = IN_CLASSA_HOST;
    else if(IN_CLASSB(net))
        mask = IN_CLASSB_HOST;
    else
        mask = IN_CLASSC_HOST;
    for(ia = in_ifaddr; ia; ia = ia->ia_next)
        if((ia->ia_netmask & net) == ia->ia_net) {
            mask = ~ia->ia_subnetmask;
            break;
        }
    __tmp.addr = htonl(net | (host & mask));
    return __tmp.in_addr;
}

/*
 * Return the network number from an internet address.
 */
u_long
in_netof(in)
struct in_addr in;
{
    register u_long i = ntohl(in.s_addr);
    register u_long net;
    register struct in_ifaddr *ia;

    if(IN_CLASSA(i))
        net = i & IN_CLASSA_NET;
    else if(IN_CLASSB(i))
        net = i & IN_CLASSB_NET;
    else if(IN_CLASSC(i))
        net = i & IN_CLASSC_NET;
    else
        return (0);

    /*
     * Check whether network is a subnet;
     * if so, return subnet number.
     */
    for(ia = in_ifaddr; ia; ia = ia->ia_next)
        if(net == ia->ia_net)
            return (i & ia->ia_subnetmask);
    return (net);
}

/*
 * Compute and save network mask as sockaddr from an internet address.
 */
void
in_sockmaskof(in, sockmask)
struct in_addr in;
register struct sockaddr_in *sockmask;
{
    register u_long net;
    register u_long mask;
    {
        register u_long i = ntohl(in.s_addr);

        if(i == 0)
            net = 0, mask = 0;
        else if(IN_CLASSA(i))
            net = i & IN_CLASSA_NET, mask = IN_CLASSA_NET;
        else if(IN_CLASSB(i))
            net = i & IN_CLASSB_NET, mask = IN_CLASSB_NET;
        else if(IN_CLASSC(i))
            net = i & IN_CLASSC_NET, mask = IN_CLASSC_NET;
        else
            net = i, mask = -1;
    }
    {
        register struct in_ifaddr *ia;
        /*
         * Check whether network is a subnet;
         * if so, return subnet number.
         */
        for(ia = in_ifaddr; ia; ia = ia->ia_next)
            if(net == ia->ia_net)
                mask =  ia->ia_subnetmask;
    }
    {
        register char *cpbase = (char *) & (sockmask->sin_addr);
        register char *cp = (char *)(1 + & (sockmask->sin_addr));

        sockmask->sin_addr.s_addr = htonl(mask);
        sockmask->sin_len = 0;
        while(--cp >= cpbase)
            if(*cp) {
                sockmask->sin_len = 1 + cp - (caddr_t)sockmask;
                break;
            }
    }
}

/*
 * Return the host portion of an internet address.
 */
u_long
in_lnaof(in)
struct in_addr in;
{
    register u_long i = ntohl(in.s_addr);
    register u_long net, host;
    register struct in_ifaddr *ia;

    if(IN_CLASSA(i)) {
        net = i & IN_CLASSA_NET;
        host = i & IN_CLASSA_HOST;
    } else if(IN_CLASSB(i)) {
        net = i & IN_CLASSB_NET;
        host = i & IN_CLASSB_HOST;
    } else if(IN_CLASSC(i)) {
        net = i & IN_CLASSC_NET;
        host = i & IN_CLASSC_HOST;
    } else
        return (i);

    /*
     * Check whether network is a subnet;
     * if so, use the modified interpretation of `host'.
     */
    for(ia = in_ifaddr; ia; ia = ia->ia_next)
        if(net == ia->ia_net)
            return (host & ~ ia->ia_subnetmask);
    return (host);
}

#ifndef SUBNETSARELOCAL
#define	SUBNETSARELOCAL	1
#endif
int subnetsarelocal = SUBNETSARELOCAL;
/*
 * Return 1 if an internet address is for a ``local'' host
 * (one to which we have a connection).  If subnetsarelocal
 * is true, this includes other subnets of the local net.
 * Otherwise, it includes only the directly-connected (sub)nets.
 */
int
in_localaddr(in)
struct in_addr in;
{
    register u_long i = ntohl(in.s_addr);
    register struct in_ifaddr *ia;

    if(subnetsarelocal) {
        for(ia = in_ifaddr; ia; ia = ia->ia_next)
            if((i & ia->ia_netmask) == ia->ia_net)
                return (1);
    } else {
        for(ia = in_ifaddr; ia; ia = ia->ia_next)
            if((i & ia->ia_subnetmask) == ia->ia_subnet)
                return (1);
    }
    return (0);
}

/*
 * Determine whether an IP address is in a reserved set of addresses
 * that may not be forwarded, or whether datagrams to that destination
 * may be forwarded.
 */
int
in_canforward(in)
struct in_addr in;
{
    register u_long i = ntohl(in.s_addr);

    if(IN_EXPERIMENTAL(i))
        return (0);
    if(IN_CLASSA(i)) {
        register u_long net = i & IN_CLASSA_NET;
        if(net == 0 || net == IN_LOOPBACKNET)
            return (0);
    }
    return (1);
}

int	in_interfaces;		/* number of external internet interfaces */
extern	struct ifnet loif;

/*
 * Generic internet control operations (ioctl's).
 * Ifp is 0 if not an interface-specific ioctl.
 */
int
in_control(so, cmd, data, ifp)
struct socket *so;
int cmd;
caddr_t data;
register struct ifnet *ifp;
{
    register struct ifreq *ifr = (struct ifreq *)data;
    register struct in_ifaddr *ia = 0;
    register struct ifaddr *ifa;
    struct in_ifaddr *oia;
    struct in_aliasreq *ifra = (struct in_aliasreq *)data;
    int error, hostIsNew, maskIsNew;
    u_long i;

    /*
     * Find address for this interface, if it exists.
     */
    if(ifp)
        for(ia = in_ifaddr; ia; ia = ia->ia_next)
            if(ia->ia_ifp == ifp)
                break;

    switch(cmd) {

    case SIOCAIFADDR:
    case SIOCDIFADDR:
        if(ifra->ifra_addr.sin_family == AF_INET)
            for(; ia; ia = ia->ia_next) {
                if(ia->ia_ifp == ifp  &&
                        ia->ia_addr.sin_addr.s_addr ==
                        ifra->ifra_addr.sin_addr.s_addr)
                    break;
            }
        if(cmd == SIOCDIFADDR && ia == 0)
            return (EADDRNOTAVAIL);
    /* FALLTHROUGH */
    case SIOCSIFADDR:
    case SIOCSIFNETMASK:
    case SIOCSIFDSTADDR:
        /*		if ((so->so_state & SS_PRIV) == 0)
        			return (EPERM);*/

        if(ifp == 0)
            panic("in_control");
        if(ia == (struct in_ifaddr *)0) {
            struct mbuf *m = m_getclr(M_WAIT, MT_IFADDR);
            if(m == (struct mbuf *)NULL)
                return (ENOBUFS);
            if(ia = in_ifaddr) {
                for(; ia->ia_next; ia = ia->ia_next)
                    ;
                ia->ia_next = mtod(m, struct in_ifaddr *);
            } else
                in_ifaddr = mtod(m, struct in_ifaddr *);
            ia = mtod(m, struct in_ifaddr *);
            if(ifa = ifp->if_addrlist) {
                for(; ifa->ifa_next; ifa = ifa->ifa_next)
                    ;
                ifa->ifa_next = (struct ifaddr *) ia;
            } else
                ifp->if_addrlist = (struct ifaddr *) ia;
            ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr;
            ia->ia_ifa.ifa_dstaddr
                = (struct sockaddr *)&ia->ia_dstaddr;
            ia->ia_ifa.ifa_netmask
                = (struct sockaddr *)&ia->ia_sockmask;
            ia->ia_sockmask.sin_len = 8;
            if(ifp->if_flags & IFF_BROADCAST) {
                ia->ia_broadaddr.sin_len = sizeof(ia->ia_addr);
                ia->ia_broadaddr.sin_family = AF_INET;
            }
            ia->ia_ifp = ifp;
            if(ifp != &loif)
                in_interfaces++;
        }
        break;

    case SIOCSIFBRDADDR:
    /*		if ((so->so_state & SS_PRIV) == 0)
    			return (EPERM);*/
    /* FALLTHROUGH */

    case SIOCGIFADDR:
    case SIOCGIFNETMASK:
    case SIOCGIFDSTADDR:
    case SIOCGIFBRDADDR:
        if(ia == (struct in_ifaddr *)0)
            return (EADDRNOTAVAIL);
        break;

    default:
        return (EOPNOTSUPP);
        break;
    }
    switch(cmd) {

    case SIOCGIFADDR: {
        struct sockaddr_in *ifr_saddr = (struct sockaddr_in *)&ifr->ifr_addr;
        *ifr_saddr = ia->ia_addr;
    }
    break;

    case SIOCGIFBRDADDR: {
        struct sockaddr_in *ifrdst_saddr = (struct sockaddr_in *)&ifr->ifr_dstaddr;
        if((ifp->if_flags & IFF_BROADCAST) == 0)
            return (EINVAL);
        *ifrdst_saddr = ia->ia_broadaddr;
    }
    break;

    case SIOCGIFDSTADDR: {
        struct sockaddr_in *ifrdst_saddr = (struct sockaddr_in *)&ifr->ifr_dstaddr;
        if((ifp->if_flags & IFF_POINTOPOINT) == 0)
            return (EINVAL);
        *ifrdst_saddr = ia->ia_dstaddr;
    }
    break;

    case SIOCGIFNETMASK: {
        struct sockaddr_in *ifr_saddr = (struct sockaddr_in *)&ifr->ifr_addr;
        *ifr_saddr = ia->ia_sockmask;
    }
    break;

    case SIOCSIFDSTADDR: {
        struct sockaddr_in *ifrdst_saddr = (struct sockaddr_in *)&ifr->ifr_dstaddr;
        struct sockaddr_in oldaddr;
        if((ifp->if_flags & IFF_POINTOPOINT) == 0)
            return (EINVAL);
        oldaddr = ia->ia_dstaddr;
        ia->ia_dstaddr = *ifrdst_saddr;
        if(ifp->if_ioctl &&
                (error = (*ifp->if_ioctl)(ifp, SIOCSIFDSTADDR, (caddr_t)ia))) {
            ia->ia_dstaddr = oldaddr;
            return (error);
        }
        if(ia->ia_flags & IFA_ROUTE) {
            ia->ia_ifa.ifa_dstaddr = (struct sockaddr *)&oldaddr;
            rtinit(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST);
            ia->ia_ifa.ifa_dstaddr =
                (struct sockaddr *)&ia->ia_dstaddr;
            rtinit(&(ia->ia_ifa), (int)RTM_ADD, RTF_HOST | RTF_UP);
        }
    }
    break;

    case SIOCSIFBRDADDR: {
        struct sockaddr_in *ifrbrd_saddr = (struct sockaddr_in *)&ifr->ifr_broadaddr;
        if((ifp->if_flags & IFF_BROADCAST) == 0)
            return (EINVAL);
        ia->ia_broadaddr = *ifrbrd_saddr;
    }
    break;

    case SIOCSIFADDR:
        return (in_ifinit(ifp, ia,
                          (struct sockaddr_in *) &ifr->ifr_addr, 1));

    case SIOCSIFNETMASK:
        i = ifra->ifra_addr.sin_addr.s_addr;
        ia->ia_subnetmask = ntohl(ia->ia_sockmask.sin_addr.s_addr = i);
        break;

    case SIOCAIFADDR:
        maskIsNew = 0;
        hostIsNew = 1;
        error = 0;
        if(ia->ia_addr.sin_family == AF_INET) {
            if(ifra->ifra_addr.sin_len == 0) {
                ifra->ifra_addr = ia->ia_addr;
                hostIsNew = 0;
            } else if(ifra->ifra_addr.sin_addr.s_addr ==
                      ia->ia_addr.sin_addr.s_addr)
                hostIsNew = 0;
        }
        if(ifra->ifra_mask.sin_len) {
            in_ifscrub(ifp, ia);
            ia->ia_sockmask = ifra->ifra_mask;
            ia->ia_subnetmask =
                ntohl(ia->ia_sockmask.sin_addr.s_addr);
            maskIsNew = 1;
        }
        if((ifp->if_flags & IFF_POINTOPOINT) &&
                (ifra->ifra_dstaddr.sin_family == AF_INET)) {
            in_ifscrub(ifp, ia);
            ia->ia_dstaddr = ifra->ifra_dstaddr;
            maskIsNew  = 1; /* We lie; but the effect's the same */
        }
        if(ifra->ifra_addr.sin_family == AF_INET &&
                (hostIsNew || maskIsNew))
            error = in_ifinit(ifp, ia, &ifra->ifra_addr, 0);
        if((ifp->if_flags & IFF_BROADCAST) &&
                (ifra->ifra_broadaddr.sin_family == AF_INET))
            ia->ia_broadaddr = ifra->ifra_broadaddr;
        if(error == 0)
            rt_newaddrmsg(RTM_NEWADDR, &ia->ia_ifa, 0, NULL);
        return (error);

    case SIOCDIFADDR:
        rt_newaddrmsg(RTM_DELADDR, (struct ifaddr *)ia, 0, NULL);
        in_ifscrub(ifp, ia);
        if((ifa = ifp->if_addrlist) == (struct ifaddr *)ia)
            ifp->if_addrlist = ifa->ifa_next;
        else {
            while(ifa->ifa_next &&
                    (ifa->ifa_next != (struct ifaddr *)ia))
                ifa = ifa->ifa_next;
            if(ifa->ifa_next)
                ifa->ifa_next = ((struct ifaddr *)ia)->ifa_next;
            else
                printf("Couldn't unlink inifaddr from ifp\n");
        }
        oia = ia;
        if(oia == (ia = in_ifaddr))
            in_ifaddr = ia->ia_next;
        else {
            while(ia->ia_next && (ia->ia_next != oia))
                ia = ia->ia_next;
            if(ia->ia_next)
                ia->ia_next = oia->ia_next;
            else
                printf("Didn't unlink inifadr from list\n");
        }
        (void) m_free(dtom(oia));
        break;

    default:
        if(ifp == 0 || ifp->if_ioctl == 0)
            return (EOPNOTSUPP);
        return ((*ifp->if_ioctl)(ifp, cmd, data));
    }
    return (0);
}

/*
 * Delete any existing route for an interface.
 */
void
in_ifscrub(ifp, ia)
register struct ifnet *ifp;
register struct in_ifaddr *ia;
{

    if((ia->ia_flags & IFA_ROUTE) == 0)
        return;
    if(ifp->if_flags & (IFF_LOOPBACK | IFF_POINTOPOINT))
        rtinit(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST);
    else
        rtinit(&(ia->ia_ifa), (int)RTM_DELETE, 0);
    ia->ia_flags &= ~IFA_ROUTE;
}

/*
 * Initialize an interface's internet address
 * and routing table entry.
 */
int
in_ifinit(ifp, ia, sin, scrub)
register struct ifnet *ifp;
register struct in_ifaddr *ia;
struct sockaddr_in *sin;
int scrub;
{
    register u_long i = ntohl(sin->sin_addr.s_addr);
    struct sockaddr_in oldaddr;
    int error, flags = RTF_UP;
    spl_t s = splimp();

    oldaddr = ia->ia_addr;
    ia->ia_addr = *sin;
    /*
     * Give the interface a chance to initialize
     * if this is its first address,
     * and to validate the address if necessary.
     */
    if(ifp->if_ioctl
            && (error = (*ifp->if_ioctl)(ifp, SIOCSIFADDR, (caddr_t)ia))) {
        splx(s);
        ia->ia_addr = oldaddr;
        return (error);
    }
    splx(s);
    if(scrub) {
        ia->ia_ifa.ifa_addr = (struct sockaddr *)&oldaddr;
        in_ifscrub(ifp, ia);
        ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr;
    }
    if(IN_CLASSA(i))
        ia->ia_netmask = IN_CLASSA_NET;
    else if(IN_CLASSB(i))
        ia->ia_netmask = IN_CLASSB_NET;
    else
        ia->ia_netmask = IN_CLASSC_NET;
    /*
     * The subnet mask usually includes at least the standard network part,
     * but may may be smaller in the case of supernetting.
     * If it is set, we believe it.
     */
    if(ia->ia_subnetmask == 0) {
        ia->ia_subnetmask = ia->ia_netmask;
        ia->ia_sockmask.sin_addr.s_addr = htonl(ia->ia_subnetmask);
    } else
        ia->ia_netmask &= ia->ia_subnetmask;
    ia->ia_net = i & ia->ia_netmask;
    ia->ia_subnet = i & ia->ia_subnetmask;
    {
        register char *cp = (char *)(1 + & (ia->ia_sockmask.sin_addr));
        register char *cpbase = (char *) & (ia->ia_sockmask.sin_addr);
        while(--cp >= cpbase)
            if(*cp) {
                ia->ia_sockmask.sin_len =
                    1 + cp - (char *) & (ia->ia_sockmask);
                break;
            }
    }
    /*
     * Add route for the network.
     */
    if(ifp->if_flags & IFF_BROADCAST) {
        ia->ia_broadaddr.sin_addr =
            in_makeaddr(ia->ia_subnet, INADDR_BROADCAST);
        ia->ia_netbroadcast.s_addr =
            htonl(ia->ia_net | (INADDR_BROADCAST & ~ ia->ia_netmask));
    } else if(ifp->if_flags & IFF_LOOPBACK) {
        ia->ia_ifa.ifa_dstaddr = ia->ia_ifa.ifa_addr;
        flags |= RTF_HOST;
    } else if(ifp->if_flags & IFF_POINTOPOINT) {
        if(ia->ia_dstaddr.sin_family != AF_INET)
            return (0);
        flags |= RTF_HOST;
    }
    if((error = rtinit(&(ia->ia_ifa), (int)RTM_ADD, flags)) == 0)
        ia->ia_flags |= IFA_ROUTE;
    return (error);
}

/*
 * Return address info for specified internet network.
 */
struct in_ifaddr *
in_iaonnetof(net)
u_long net;
{
    register struct in_ifaddr *ia;

    for(ia = in_ifaddr; ia; ia = ia->ia_next)
        if(ia->ia_subnet == net)
            return (ia);
    return ((struct in_ifaddr *)0);
}

/*
 * Return 1 if the address might be a local broadcast address.
 */
int
in_broadcast(in)
struct in_addr in;
{
    register struct in_ifaddr *ia;

    /*
     * Look through the list of addresses for a match
     * with a broadcast address.
     */
    for(ia = in_ifaddr; ia; ia = ia->ia_next)
        if(ia->ia_ifp->if_flags & IFF_BROADCAST) {
            u_long t;
            if(ia->ia_broadaddr.sin_addr.s_addr == in.s_addr)
                return (1);
            /*
             * Check for old-style (host 0) broadcast.
             */
            if((t = ntohl(in.s_addr)) == ia->ia_subnet || t == ia->ia_net)
                return (1);
        }
    if(in.s_addr == INADDR_BROADCAST || in.s_addr == INADDR_ANY)
        return (1);
    return (0);
}

#ifdef ENABLE_MULTICAST
/*
 * in_mcast_macaddr - compute the Ethernet multicast MAC for an IPv4 group.
 * RFC 1112 §6.4: map a class-D address to 01:00:5e:XX:XX:XX using the low
 * 23 bits of the group address.
 */
static void
in_mcast_macaddr(struct in_addr *maddr, u_int8_t *mac)
{
    u_long addr = ntohl(maddr->s_addr);

    mac[0] = 0x01;
    mac[1] = 0x00;
    mac[2] = 0x5e;
    mac[3] = (addr >> 16) & 0x7f;
    mac[4] = (addr >> 8) & 0xff;
    mac[5] = addr & 0xff;
}

/*
 * sana_add_mcast - tell the SANA-II device to accept the Ethernet multicast
 * for an IPv4 multicast group via S2_ADDMULTICASTADDRESS, so the NIC hardware
 * filter passes matching frames.  Mirrors sana_add_mcast6() in netinet6/in6.c.
 */
static int
sana_add_mcast(struct ifnet *ifp, struct in_addr *maddr)
{
    struct sana_softc *ssc = (struct sana_softc *)ifp;
    struct IOSana2Req *req;
    u_int8_t mac[6];
    int error = 0;

    if(ifp->if_addrlen != 6)
        return 0;  /* not Ethernet -- nothing to do */

    in_mcast_macaddr(maddr, mac);

    D(bug("[AROSTCP:IN] %s: %s%d registering eth mcast %02x:%02x:%02x:%02x:%02x:%02x\n",
          __func__, ifp->if_name, ifp->if_unit,
          mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]));

    req = CreateIOSana2Req(ssc);
    if(req == NULL)
        return ENOBUFS;

    req->ios2_Req.io_Command = S2_ADDMULTICASTADDRESS;
    bcopy(mac, req->ios2_SrcAddr, 6);
    DoIO((struct IORequest *)req);

    if(req->ios2_Req.io_Error)
        error = EIO;

    DeleteIOSana2Req(req);
    return error;
}

/*
 * sana_del_mcast - stop the SANA-II device accepting an Ethernet multicast.
 */
static int
sana_del_mcast(struct ifnet *ifp, struct in_addr *maddr)
{
    struct sana_softc *ssc = (struct sana_softc *)ifp;
    struct IOSana2Req *req;
    u_int8_t mac[6];
    int error = 0;

    if(ifp->if_addrlen != 6)
        return 0;

    in_mcast_macaddr(maddr, mac);

    req = CreateIOSana2Req(ssc);
    if(req == NULL)
        return ENOBUFS;

    req->ios2_Req.io_Command = S2_DELMULTICASTADDRESS;
    bcopy(mac, req->ios2_SrcAddr, 6);
    DoIO((struct IORequest *)req);

    if(req->ios2_Req.io_Error)
        error = EIO;

    DeleteIOSana2Req(req);
    return error;
}

/*
 * in_addmulti - add (or refcount) an IPv4 multicast membership on an
 * interface.  On the first reference the corresponding Ethernet multicast is
 * registered with the SANA-II device and an unsolicited IGMP report is sent.
 * Mirrors in6_addmulti() in netinet6/in6.c.
 */
struct in_multi *in_multihead = NULL;

struct in_multi *
in_addmulti(struct in_addr *maddr, struct ifnet *ifp)
{
    struct in_multi *inm;

    /* search for an existing entry */
    for(inm = in_multihead; inm != NULL; inm = inm->inm_next) {
        if(inm->inm_ifp == ifp &&
           inm->inm_addr.s_addr == maddr->s_addr) {
            inm->inm_refcount++;
            return inm;
        }
    }

    /* register the Ethernet multicast address with the NIC */
    if(sana_add_mcast(ifp, maddr) != 0) {
        __log(LOG_ERR, "in_addmulti: S2_ADDMULTICASTADDRESS failed for %s%d\n",
              ifp->if_name, ifp->if_unit);
        /* continue anyway -- some drivers accept all multicast */
    }

    /* allocate a new entry */
    MALLOC(inm, struct in_multi *, sizeof(*inm), M_IFADDR, M_WAITOK);
    if(inm == NULL)
        return NULL;
    bzero(inm, sizeof(*inm));
    inm->inm_addr     = *maddr;
    inm->inm_ifp      = ifp;
    inm->inm_refcount = 1;
    inm->inm_state    = IGMP_OTHERMEMBER;
    inm->inm_timer    = 0;
    inm->inm_next     = in_multihead;
    in_multihead      = inm;

    /* announce our membership (unsolicited IGMP report) */
    igmp_joingroup(inm);

    return inm;
}

/*
 * in_delmulti - drop a reference to an IPv4 multicast membership.  When the
 * last reference is released an IGMP Leave is sent (if we were the last
 * reporter), the Ethernet multicast is unregistered and the entry is freed.
 * Mirrors in6_delmulti() in netinet6/in6.c.
 */
void
in_delmulti(struct in_multi *inm)
{
    struct in_multi **p;

    if(--inm->inm_refcount > 0)
        return;

    /* announce that we are leaving before removing from the list */
    igmp_leavegroup(inm);

    /* unregister the Ethernet multicast address from the NIC */
    sana_del_mcast(inm->inm_ifp, &inm->inm_addr);

    for(p = &in_multihead; *p != NULL; p = &(*p)->inm_next) {
        if(*p == inm) {
            *p = inm->inm_next;
            break;
        }
    }
    FREE(inm, M_IFADDR);
}
#endif /* ENABLE_MULTICAST */
#endif
