/* dlpi.c
 
   Data Link Provider Interface (DLPI) network interface code. */

/*
 * Copyright (c) 2004,2007,2009 by Internet Systems Consortium, Inc. ("ISC")
 * Copyright (c) 1996-2003 by Internet Software Consortium
 *
 * 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 ISC DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC 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.
 *
 *   Internet Systems Consortium, Inc.
 *   950 Charter Street
 *   Redwood City, CA 94063
 *   <info@isc.org>
 *   https://www.isc.org/
 *
 * This software was written for Internet Systems Consortium
 * by Eric James Negaard, <lmdejn@lmd.ericsson.se>.  To learn more about
 * Internet Systems Consortium, see ``https://www.isc.org''.
 *
 * Joost Mulders has also done considerable work in debugging the DLPI API
 * support on Solaris and getting this code to work properly on a variety
 * of different Solaris platforms.
 */

/*
 * Based largely in part to the existing NIT code in nit.c.
 *
 * This code has been developed and tested on sparc-based machines running
 * SunOS 5.5.1, with le and hme network interfaces.  It should be pretty
 * generic, though.
 */

/*
 * Implementation notes:
 *
 * I first tried to write this code to the "vanilla" DLPI 2.0 API.
 * It worked on a Sun Ultra-1 with a hme interface, but didn't work
 * on Sun SparcStation 5's with "le" interfaces (the packets sent out
 * via dlpiunitdatareq contained an Ethernet type of 0x0000 instead
 * of the expected 0x0800).
 *
 * Therefore I added the "DLPI_RAW" code which is a Sun extension to
 * the DLPI standard.  This code works on both of the above machines.
 * This is configurable in the OS-dependent include file by defining
 * USE_DLPI_RAW.
 *
 * It quickly became apparant that I should also use the "pfmod"
 * STREAMS module to cut down on the amount of user level packet
 * processing.  I don't know how widely available "pfmod" is, so it's
 * use is conditionally included. This is configurable in the
 * OS-dependent include file by defining USE_DLPI_PFMOD.
 *
 * A major quirk on the Sun's at least, is that no packets seem to get
 * sent out the interface until six seconds after the interface is
 * first "attached" to [per system reboot] (it's actually from when
 * the interface is attached, not when it is plumbed, so putting a
 * sleep into the dhclient-script at PREINIT time doesn't help).  I
 * HAVE tried, without success to poll the fd to see when it is ready
 * for writing.  This doesn't help at all. If the sleeps are not done,
 * the initial DHCPREQUEST or DHCPDISCOVER never gets sent out, so
 * I've put them here, when register_send and register_receive are
 * called (split up into two three-second sleeps between the notices,
 * so that it doesn't seem like so long when you're watching :-).  The
 * amount of time to sleep is configurable in the OS-dependent include
 * file by defining DLPI_FIRST_SEND_WAIT to be the number of seconds
 * to sleep.
 */

/*
 * The Open Group Technical Standard can be found here:
 * http://www.opengroup.org/onlinepubs/009618899/index.htm
 *
 * The HP DLPI Programmer's Guide can be found here:
 * http://docs.hp.com/en/B2355-90139/index.html
 */

#include "dhcpd.h"

#if defined (USE_DLPI_SEND) || defined (USE_DLPI_RECEIVE) || \
    defined(USE_DLPI_HWADDR)

# include <sys/ioctl.h>
# include <sys/time.h>
# include <sys/dlpi.h>
# include <stropts.h>
# ifdef USE_DLPI_PFMOD
#  include <sys/pfmod.h>
# endif
#include <poll.h>
#include <errno.h>

# include <netinet/in_systm.h>
# include "includes/netinet/ip.h"
# include "includes/netinet/udp.h"
# include "includes/netinet/if_ether.h"

# ifdef USE_DLPI_PFMOD
#  ifdef USE_DLPI_RAW
#   define DLPI_MODNAME "DLPI+RAW+PFMOD"
#  else
#   define DLPI_MODNAME "DLPI+PFMOD"
#  endif
# else
#  ifdef USE_DLPI_RAW
#   define DLPI_MODNAME "DLPI+RAW"
#  else
#   define DLPI_MODNAME "DLPI"
#  endif
# endif

# ifndef ABS
#  define ABS(x) ((x) >= 0 ? (x) : 0-(x))
# endif

#if defined(USE_DLPI_PFMOD) || defined(USE_DLPI_RAW)
static int strioctl PROTO ((int fd, int cmd, int timeout, int len, char *dp));
#endif

#define DLPI_MAXDLBUF		8192	/* Buffer size */
#define DLPI_MAXDLADDR		1024	/* Max address size */
#define DLPI_DEVDIR		"/dev/"	/* Device directory */

static int dlpiopen(const char *ifname);
static int dlpiunit PROTO ((char *ifname));
static int dlpiinforeq PROTO ((int fd));
static int dlpiphysaddrreq PROTO ((int fd, unsigned long addrtype));
static int dlpiattachreq PROTO ((int fd, unsigned long ppa));
static int dlpibindreq PROTO ((int fd, unsigned long sap, unsigned long max_conind,
			       unsigned long service_mode, unsigned long conn_mgmt,
			       unsigned long xidtest));
#if defined(UNUSED_DLPI_INTERFACE)
/* These functions are unused at present, but may be used at a later date.
 * defined out to avoid compiler warnings about unused static functions.
 */
static int dlpidetachreq PROTO ((int fd));
static int dlpiunbindreq PROTO ((int fd));
#endif
static int dlpiokack PROTO ((int fd, char *bufp));
static int dlpiinfoack PROTO ((int fd, char *bufp));
static int dlpiphysaddrack PROTO ((int fd, char *bufp));
static int dlpibindack PROTO ((int fd, char *bufp));
#if defined(USE_DLPI_SEND) || defined(USE_DLPI_RECEIVE)
/* These functions are not used if we're only sourcing the get_hw_addr()
 * function (for USE_SOCKETS).
 */
static int dlpiunitdatareq PROTO ((int fd, unsigned char *addr,
				   int addrlen, unsigned long minpri,
				   unsigned long maxpri, unsigned char *data,
				   int datalen));
static int dlpiunitdataind PROTO ((int fd,
				   unsigned char *dstaddr,
				   unsigned long *dstaddrlen,
				   unsigned char *srcaddr,
				   unsigned long *srcaddrlen,
				   unsigned long *grpaddr,
				   unsigned char *data,
				   int datalen));
#endif /* !USE_DLPI_HWADDR: USE_DLPI_SEND || USE_DLPI_RECEIVE */
static int	expected PROTO ((unsigned long prim, union DL_primitives *dlp,
				  int msgflags));
static int	strgetmsg PROTO ((int fd, struct strbuf *ctlp,
				  struct strbuf *datap, int *flagsp,
				  char *caller));

/* Reinitializes the specified interface after an address change.   This
   is not required for packet-filter APIs. */

#ifdef USE_DLPI_SEND
void if_reinitialize_send (info)
	struct interface_info *info;
{
}
#endif

#ifdef USE_DLPI_RECEIVE
void if_reinitialize_receive (info)
	struct interface_info *info;
{
}
#endif

/* Called by get_interface_list for each interface that's discovered.
   Opens a packet filter for each interface and adds it to the select
   mask. */

int if_register_dlpi (info)
	struct interface_info *info;
{
	int sock;
	int unit;
	long buf [DLPI_MAXDLBUF];
	union DL_primitives *dlp;

	dlp = (union DL_primitives *)buf;

	/* Open a DLPI device */
	if ((sock = dlpiopen (info -> name)) < 0) {
	    log_fatal ("Can't open DLPI device for %s: %m", info -> name);
	}

	/*
	 * Submit a DL_INFO_REQ request, to find the dl_mac_type and 
         * dl_provider_style
	 */
	if (dlpiinforeq(sock) < 0 || dlpiinfoack(sock, (char *)buf) < 0) {
	    log_fatal ("Can't get DLPI MAC type for %s: %m", info -> name);
	} else {
	    switch (dlp -> info_ack.dl_mac_type) {
	      case DL_CSMACD: /* IEEE 802.3 */
	      case DL_ETHER:
		info -> hw_address.hbuf [0] = HTYPE_ETHER;
		break;
	      /* adding token ring 5/1999 - mayer@ping.at  */ 
	      case DL_TPR:
		info -> hw_address.hbuf [0] = HTYPE_IEEE802;
		break;
	      case DL_FDDI:
		info -> hw_address.hbuf [0] = HTYPE_FDDI;
		break;
	      default:
		log_fatal("%s: unsupported DLPI MAC type %lu", info->name,
			  (unsigned long)dlp->info_ack.dl_mac_type);
		break;
	    }
            /*
             * copy the sap length and broadcast address of this interface
             * to interface_info. This fixes nothing but seemed nicer than to
             * assume -2 and ffffff.
             */
            info -> dlpi_sap_length = dlp -> info_ack.dl_sap_length;
            info -> dlpi_broadcast_addr.hlen = 
             dlp -> info_ack.dl_brdcst_addr_length;
            memcpy (info -> dlpi_broadcast_addr.hbuf, 
             (char *)dlp + dlp -> info_ack.dl_brdcst_addr_offset, 
             dlp -> info_ack.dl_brdcst_addr_length);
	}

	if (dlp -> info_ack.dl_provider_style == DL_STYLE2) {
	    /*
	     * Attach to the device.  If this fails, the device
	     * does not exist.
	     */
	    unit = dlpiunit (info -> name);
	
	    if (dlpiattachreq (sock, unit) < 0
		|| dlpiokack (sock, (char *)buf) < 0) {
		log_fatal ("Can't attach DLPI device for %s: %m", info -> name);
	    }
	}

	/*
	 * Bind to the IP service access point (SAP), connectionless (CLDLS).
	 */
	if (dlpibindreq (sock, ETHERTYPE_IP, 0, DL_CLDLS, 0, 0) < 0
	    || dlpibindack (sock, (char *)buf) < 0) {
	    log_fatal ("Can't bind DLPI device for %s: %m", info -> name);
	}

	/*
	 * Submit a DL_PHYS_ADDR_REQ request, to find
	 * the hardware address
	 */
	if (dlpiphysaddrreq (sock, DL_CURR_PHYS_ADDR) < 0
	    || dlpiphysaddrack (sock, (char *)buf) < 0) {
	    log_fatal ("Can't get DLPI hardware address for %s: %m",
		   info -> name);
	}

	info -> hw_address.hlen = dlp -> physaddr_ack.dl_addr_length + 1;
	memcpy (&info -> hw_address.hbuf [1],
		(char *)buf + dlp -> physaddr_ack.dl_addr_offset,
		dlp -> physaddr_ack.dl_addr_length);

#ifdef USE_DLPI_RAW
	if (strioctl (sock, DLIOCRAW, INFTIM, 0, 0) < 0) {
	    log_fatal ("Can't set DLPI RAW mode for %s: %m",
		   info -> name);
	}
#endif

#ifdef USE_DLPI_PFMOD
	if (ioctl (sock, I_PUSH, "pfmod") < 0) {
	    log_fatal ("Can't push packet filter onto DLPI for %s: %m",
		   info -> name);
	}
#endif

	return sock;
}

#if defined(USE_DLPI_PFMOD) || defined(USE_DLPI_RAW)
static int
strioctl (fd, cmd, timeout, len, dp)
int fd;
int cmd;
int timeout;
int len;
char *dp;
{
    struct strioctl sio;
    int rslt;

    sio.ic_cmd = cmd;
    sio.ic_timout = timeout;
    sio.ic_len = len;
    sio.ic_dp = dp;

    if ((rslt = ioctl (fd, I_STR, &sio)) < 0) {
	return rslt;
    } else {
	return sio.ic_len;
    }
}
#endif /* USE_DPI_PFMOD || USE_DLPI_RAW */

#ifdef USE_DLPI_SEND
void if_register_send (info)
	struct interface_info *info;
{
	/* If we're using the DLPI API for sending and receiving,
	   we don't need to register this interface twice. */
#ifndef USE_DLPI_RECEIVE
# ifdef USE_DLPI_PFMOD
	struct packetfilt pf;
# endif

	info -> wfdesc = if_register_dlpi (info);

# ifdef USE_DLPI_PFMOD
	/* Set up an PFMOD filter that rejects everything... */
	pf.Pf_Priority = 0;
	pf.Pf_FilterLen = 1;
	pf.Pf_Filter [0] = ENF_PUSHZERO;

	/* Install the filter */
	if (strioctl (info -> wfdesc, PFIOCSETF, INFTIM,
		      sizeof (pf), (char *)&pf) < 0) {
	    log_fatal ("Can't set PFMOD send filter on %s: %m", info -> name);
	}

# endif /* USE_DLPI_PFMOD */
#else /* !defined (USE_DLPI_RECEIVE) */
	/*
	 * If using DLPI for both send and receive, simply re-use
	 * the read file descriptor that was set up earlier.
	 */
	info -> wfdesc = info -> rfdesc;
#endif

        if (!quiet_interface_discovery)
		log_info ("Sending on   DLPI/%s/%s%s%s",
		      info -> name,
		      print_hw_addr (info -> hw_address.hbuf [0],
				     info -> hw_address.hlen - 1,
				     &info -> hw_address.hbuf [1]),
		      (info -> shared_network ? "/" : ""),
		      (info -> shared_network ?
		       info -> shared_network -> name : ""));

#ifdef DLPI_FIRST_SEND_WAIT
/* See the implementation notes at the beginning of this file */
# ifdef USE_DLPI_RECEIVE
	sleep (DLPI_FIRST_SEND_WAIT - (DLPI_FIRST_SEND_WAIT / 2));
# else
	sleep (DLPI_FIRST_SEND_WAIT);
# endif
#endif
}

void if_deregister_send (info)
	struct interface_info *info;
{
	/* If we're using the DLPI API for sending and receiving,
	   we don't need to register this interface twice. */
#ifndef USE_DLPI_RECEIVE
	close (info -> wfdesc);
#endif
	info -> wfdesc = -1;

        if (!quiet_interface_discovery)
		log_info ("Disabling output on DLPI/%s/%s%s%s",
		      info -> name,
		      print_hw_addr (info -> hw_address.hbuf [0],
				     info -> hw_address.hlen - 1,
				     &info -> hw_address.hbuf [1]),
		      (info -> shared_network ? "/" : ""),
		      (info -> shared_network ?
		       info -> shared_network -> name : ""));
}
#endif /* USE_DLPI_SEND */

#ifdef USE_DLPI_RECEIVE
/* Packet filter program...
   XXX Changes to the filter program may require changes to the constant
   offsets used in if_register_send to patch the NIT program! XXX */

void if_register_receive (info)
	struct interface_info *info;
{
#ifdef USE_DLPI_PFMOD
	struct packetfilt pf;
        struct ip iphdr;
        u_int16_t offset;
#endif

	/* Open a DLPI device and hang it on this interface... */
	info -> rfdesc = if_register_dlpi (info);

#ifdef USE_DLPI_PFMOD
	/* Set up the PFMOD filter program. */
	/* XXX Unlike the BPF filter program, this one won't work if the
	   XXX IP packet is fragmented or if there are options on the IP
	   XXX header. */
	pf.Pf_Priority = 0;
	pf.Pf_FilterLen = 0;

#if defined (USE_DLPI_RAW)
# define ETHER_H_PREFIX (14) /* sizeof (ethernet_header) */
    /*
     * ethertype == ETHERTYPE_IP
     */
    offset = 12;
    pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + (offset / 2);
    pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT | ENF_CAND;
    pf.Pf_Filter [pf.Pf_FilterLen++] = htons (ETHERTYPE_IP);
# else
# define ETHER_H_PREFIX (0)
# endif /* USE_DLPI_RAW */
	/*
	 * The packets that will be received on this file descriptor
	 * will be IP packets (due to the SAP that was specified in
	 * the dlbind call).  There will be no ethernet header.
	 * Therefore, setup the packet filter to check the protocol
	 * field for UDP, and the destination port number equal
	 * to the local port.  All offsets are relative to the start
	 * of an IP packet.
	 */

        /*
         * BOOTPS destination port
         */
        offset = ETHER_H_PREFIX + sizeof (iphdr) + sizeof (u_int16_t);
        pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + (offset / 2);
        pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT | ENF_CAND;
        pf.Pf_Filter [pf.Pf_FilterLen++] = local_port;

        /*
         * protocol should be udp. this is a byte compare, test for
         * endianess.
         */
        offset = ETHER_H_PREFIX + ((u_int8_t *)&(iphdr.ip_p) - (u_int8_t *)&iphdr);
        pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHWORD + (offset / 2);
        pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT | ENF_AND;
        pf.Pf_Filter [pf.Pf_FilterLen++] = htons (0x00FF);
        pf.Pf_Filter [pf.Pf_FilterLen++] = ENF_PUSHLIT | ENF_CAND;
      pf.Pf_Filter [pf.Pf_FilterLen++] = htons (IPPROTO_UDP);

	/* Install the filter... */
	if (strioctl (info -> rfdesc, PFIOCSETF, INFTIM,
		      sizeof (pf), (char *)&pf) < 0) {
	    log_fatal ("Can't set PFMOD receive filter on %s: %m", info -> name);
	}
#endif /* USE_DLPI_PFMOD */

        if (!quiet_interface_discovery)
		log_info ("Listening on DLPI/%s/%s%s%s",
		      info -> name,
		      print_hw_addr (info -> hw_address.hbuf [0],
				     info -> hw_address.hlen - 1,
				     &info -> hw_address.hbuf [1]),
		      (info -> shared_network ? "/" : ""),
		      (info -> shared_network ?
		       info -> shared_network -> name : ""));

#ifdef DLPI_FIRST_SEND_WAIT
/* See the implementation notes at the beginning of this file */
# ifdef USE_DLPI_SEND
	sleep (DLPI_FIRST_SEND_WAIT / 2);
# else
	sleep (DLPI_FIRST_SEND_WAIT);
# endif
#endif
}

void if_deregister_receive (info)
	struct interface_info *info;
{
	/* If we're using the DLPI API for sending and receiving,
	   we don't need to register this interface twice. */
#ifndef USE_DLPI_SEND
	close (info -> rfdesc);
#endif
	info -> rfdesc = -1;

        if (!quiet_interface_discovery)
		log_info ("Disabling input on DLPI/%s/%s%s%s",
		      info -> name,
		      print_hw_addr (info -> hw_address.hbuf [0],
				     info -> hw_address.hlen - 1,
				     &info -> hw_address.hbuf [1]),
		      (info -> shared_network ? "/" : ""),
		      (info -> shared_network ?
		       info -> shared_network -> name : ""));
}
#endif /* USE_DLPI_RECEIVE */

#ifdef USE_DLPI_SEND
ssize_t send_packet (interface, packet, raw, len, from, to, hto)
	struct interface_info *interface;
	struct packet *packet;
	struct dhcp_packet *raw;
	size_t len;
	struct in_addr from;
	struct sockaddr_in *to;
	struct hardware *hto;
{
#ifdef USE_DLPI_RAW
	double hh [32];
#endif
	double ih [1536 / sizeof (double)];
	unsigned char *dbuf = (unsigned char *)ih;
	unsigned dbuflen;
	unsigned char dstaddr [DLPI_MAXDLADDR];
	unsigned addrlen;
	int result;
	int fudge;

	if (!strcmp (interface -> name, "fallback"))
		return send_fallback (interface, packet, raw,
				      len, from, to, hto);

	if (hto == NULL && interface->anycast_mac_addr.hlen)
		hto = &interface->anycast_mac_addr;

	dbuflen = 0;

	/* Assemble the headers... */
#ifdef USE_DLPI_RAW
	assemble_hw_header (interface, (unsigned char *)hh, &dbuflen, hto);
      if (dbuflen > sizeof hh)
              log_fatal ("send_packet: hh buffer too small.\n");
	fudge = dbuflen % 4; /* IP header must be word-aligned. */
	memcpy (dbuf + fudge, (unsigned char *)hh, dbuflen);
	dbuflen += fudge;
#else
	fudge = 0;
#endif
	assemble_udp_ip_header (interface, dbuf, &dbuflen, from.s_addr,
				to -> sin_addr.s_addr, to -> sin_port,
				(unsigned char *)raw, len);

	/* Copy the data into the buffer (yuk). */
	memcpy (dbuf + dbuflen, raw, len);
	dbuflen += len;

#ifdef USE_DLPI_RAW
	result = write (interface -> wfdesc, dbuf + fudge, dbuflen - fudge);
#else

	/*
         * Setup the destination address (DLSAP) in dstaddr 
         *
         * If sap_length < 0 we must deliver the DLSAP as phys+sap. 
         * If sap_length > 0 we must deliver the DLSAP as sap+phys.
         *
         * sap = Service Access Point == ETHERTYPE_IP
         * sap + datalink address is called DLSAP in dlpi speak.
         */
        { /* ENCODE DLSAP */
          unsigned char phys [DLPI_MAXDLADDR];
          unsigned char sap [4];
          int sap_len = interface -> dlpi_sap_length;
          int phys_len = interface -> hw_address.hlen - 1;

          /* sap = htons (ETHERTYPE_IP) kludge */
          memset (sap, 0, sizeof (sap));
# if (BYTE_ORDER == LITTLE_ENDIAN)
          sap [0] = 0x00;
          sap [1] = 0x08;
# else
          sap [0] = 0x08;
          sap [1] = 0x00;
# endif

        if (hto && hto -> hlen == interface -> hw_address.hlen)
             memcpy ( phys, (char *) &hto -> hbuf [1], phys_len);
          else 
             memcpy ( phys, interface -> dlpi_broadcast_addr.hbuf, 
              interface -> dlpi_broadcast_addr.hlen);

          if (sap_len < 0) { 
             memcpy ( dstaddr, phys, phys_len);
             memcpy ( (char *) &dstaddr [phys_len], sap, ABS (sap_len));
          }
          else {
             memcpy ( dstaddr, (void *) sap, sap_len);
             memcpy ( (char *) &dstaddr [sap_len], phys, phys_len);
          }
        addrlen = phys_len + ABS (sap_len);
      } /* ENCODE DLSAP */

	result = dlpiunitdatareq (interface -> wfdesc, dstaddr, addrlen,
				  0, 0, dbuf, dbuflen);
#endif /* USE_DLPI_RAW */
	if (result < 0)
		log_error ("send_packet: %m");
	return result;
}
#endif /* USE_DLPI_SEND */

#ifdef USE_DLPI_RECEIVE
ssize_t receive_packet (interface, buf, len, from, hfrom)
	struct interface_info *interface;
	unsigned char *buf;
	size_t len;
	struct sockaddr_in *from;
	struct hardware *hfrom;
{
	unsigned char dbuf [1536];
	unsigned char srcaddr [DLPI_MAXDLADDR];
	unsigned long srcaddrlen;
	int length = 0;
	int offset = 0;
	int bufix = 0;
	unsigned paylen;
	
#ifdef USE_DLPI_RAW
	length = read (interface -> rfdesc, dbuf, sizeof (dbuf));
#else	
	length = dlpiunitdataind (interface -> rfdesc, (unsigned char *)NULL,
				  (unsigned long *)NULL, srcaddr, &srcaddrlen,
				  (unsigned long *)NULL, dbuf, sizeof (dbuf));
#endif

	if (length <= 0) {
	    log_error("receive_packet: %m");
	    return length;
	}

# if !defined (USE_DLPI_RAW)
        /*
         * Copy the sender's hw address into hfrom
         * If sap_len < 0 the DLSAP is as phys+sap.
         * If sap_len > 0 the DLSAP is as sap+phys.
         *
         * sap is discarded here.
         */
        { /* DECODE DLSAP */
          int sap_len = interface -> dlpi_sap_length;
          int phys_len = interface -> hw_address.hlen - 1;

          if (hfrom && (srcaddrlen == ABS (sap_len) + phys_len )) {
            hfrom -> hbuf [0] = interface -> hw_address.hbuf [0];
            hfrom -> hlen = interface -> hw_address.hlen;
            
            if (sap_len < 0) {
              memcpy ((char *) &hfrom -> hbuf [1], srcaddr, phys_len);
            }
            else {
              memcpy((char *)&hfrom->hbuf[1], srcaddr + sap_len, phys_len);
            }
          } 
          else if (hfrom) {
            memset (hfrom, '\0', sizeof *hfrom);
          }
        } /* DECODE_DLSAP */

# endif /* !defined (USE_DLPI_RAW) */

	/* Decode the IP and UDP headers... */
	bufix = 0;
#ifdef USE_DLPI_RAW
	/* Decode the physical header... */
	offset = decode_hw_header (interface, dbuf, bufix, hfrom);

	/* If a physical layer checksum failed (dunno of any
	   physical layer that supports this, but WTH), skip this
	   packet. */
	if (offset < 0) {
		return 0;
	}
	bufix += offset;
	length -= offset;
#endif
	offset = decode_udp_ip_header (interface, dbuf, bufix,
				       from, length, &paylen);

	/*
	 * If the IP or UDP checksum was bad, skip the packet...
	 *
	 * Note: this happens all the time when writing packets via the
	 * fallback socket.  The packet received by streams does not have
	 * the IP or UDP checksums filled in, as those are calculated by
	 * the hardware.
	 */
	if (offset < 0) {
		return 0;
	}

	bufix += offset;
	length -= offset;

	if (length < paylen)
		log_fatal("Internal inconsistency at %s:%d.", MDL);

	/* Copy out the data in the packet... */
	memcpy(buf, &dbuf [bufix], paylen);
	return paylen;
}
#endif

/* Common DLPI routines ...
 *
 * Written by Eric James Negaard, <lmdejn@lmd.ericsson.se>
 *
 * Based largely in part to the example code contained in the document
 * "How to Use the STREAMS Data Link Provider Interface (DLPI)", written
 * by Neal Nuckolls of SunSoft Internet Engineering.
 * 
 * This code has been developed and tested on sparc-based machines running
 * SunOS 5.5.1, with le and hme network interfaces.  It should be pretty
 * generic, though.
 * 
 * The usual disclaimers apply.  This code works for me.  Don't blame me
 * if it makes your machine or network go down in flames.  That taken
 * into consideration, use this code as you wish.  If you make usefull
 * modifications I'd appreciate hearing about it.
 */

#define DLPI_MAXWAIT		15	/* Max timeout */


/*
 * Parse an interface name and extract the unit number
 */

static int dlpiunit (ifname)
	char *ifname;
{
	char *cp;
	int unit;
	
	if (!ifname) {
		return 0;
	}
	
	/* Advance to the end of the name */
	cp = ifname;
	while (*cp) cp++;
	/* Back up to the start of the first digit */
	while ((*(cp-1) >= '0' && *(cp-1) <= '9') || *(cp - 1) == ':') cp--;
	
	/* Convert the unit number */
	unit = 0;
	while (*cp >= '0' && *cp <= '9') {
		unit *= 10;
		unit += (*cp++ - '0');
	}
	
	return unit;
}

/*
 * dlpiopen - open the DLPI device for a given interface name
 */
static int
dlpiopen(const char *ifname) {
	char devname [50];
	char *dp;
	const char *cp, *ep;
	
	if (!ifname) {
		return -1;
	}
	
	/* Open a DLPI device */
	if (*ifname == '/') {
		dp = devname;
	} else {
		/* Prepend the device directory */
		memcpy (devname, DLPI_DEVDIR, strlen (DLPI_DEVDIR));
		dp = &devname [strlen (DLPI_DEVDIR)];
	}

	/* Find the end of the interface name */
	ep = cp = ifname;
	while (*ep)
		ep++;
	/* And back up to the first digit (unit number) */
	while ((*(ep - 1) >= '0' && *(ep - 1) <= '9') || *(ep - 1) == ':')
		ep--;
	
	/* Copy everything up to the unit number */
	while (cp < ep) {
		*dp++ = *cp++;
	}
	*dp = '\0';
	
	return open (devname, O_RDWR, 0);
}

/*
 * dlpiinforeq - request information about the data link provider.
 */

static int dlpiinforeq (fd)
	int fd;
{
	dl_info_req_t info_req;
	struct strbuf ctl;
	int flags;
	
	info_req.dl_primitive = DL_INFO_REQ;
	
	ctl.maxlen = 0;
	ctl.len = sizeof (info_req);
	ctl.buf = (char *)&info_req;
	
	flags = RS_HIPRI;
	
	return putmsg (fd, &ctl, (struct strbuf *)NULL, flags);
}

/*
 * dlpiphysaddrreq - request the current physical address.
 */
static int dlpiphysaddrreq (fd, addrtype)
	int fd;
	unsigned long addrtype;
{
	dl_phys_addr_req_t physaddr_req;
	struct strbuf ctl;
	int flags;
	
	physaddr_req.dl_primitive = DL_PHYS_ADDR_REQ;
	physaddr_req.dl_addr_type = addrtype;
	
	ctl.maxlen = 0;
	ctl.len = sizeof (physaddr_req);
	ctl.buf = (char *)&physaddr_req;
	
	flags = RS_HIPRI;
	
	return putmsg (fd, &ctl, (struct strbuf *)NULL, flags);
}

/*
 * dlpiattachreq - send a request to attach to a specific unit.
 */
static int dlpiattachreq (fd, ppa)
	unsigned long ppa;
	int fd;
{
	dl_attach_req_t	attach_req;
	struct strbuf ctl;
	int flags;
	
	attach_req.dl_primitive = DL_ATTACH_REQ;
	attach_req.dl_ppa = ppa;
	
	ctl.maxlen = 0;
	ctl.len = sizeof (attach_req);
	ctl.buf = (char *)&attach_req;
	
	flags = 0;
	
	return putmsg (fd, &ctl, (struct strbuf*)NULL, flags);
}

/*
 * dlpibindreq - send a request to bind to a specific SAP address.
 */
static int dlpibindreq (fd, sap, max_conind, service_mode, conn_mgmt, xidtest)
	unsigned long sap;
	unsigned long max_conind;
	unsigned long service_mode;
	unsigned long conn_mgmt;
	unsigned long xidtest;
	int fd;
{
	dl_bind_req_t bind_req;
	struct strbuf ctl;
	int flags;
	
	bind_req.dl_primitive = DL_BIND_REQ;
	bind_req.dl_sap = sap;
	bind_req.dl_max_conind = max_conind;
	bind_req.dl_service_mode = service_mode;
	bind_req.dl_conn_mgmt = conn_mgmt;
	bind_req.dl_xidtest_flg = xidtest;
	
	ctl.maxlen = 0;
	ctl.len = sizeof (bind_req);
	ctl.buf = (char *)&bind_req;
	
	flags = 0;
	
	return putmsg (fd, &ctl, (struct strbuf*)NULL, flags);
}

#if defined(UNUSED_DLPI_INTERFACE)
/*
 * dlpiunbindreq - send a request to unbind.  This function is not actually
 *	used by ISC DHCP, but is included for completeness in case it is
 *	ever required for new work.
 */
static int dlpiunbindreq (fd)
	int fd;
{
	dl_unbind_req_t	unbind_req;
	struct strbuf ctl;
	int flags;
	
	unbind_req.dl_primitive = DL_UNBIND_REQ;
	
	ctl.maxlen = 0;
	ctl.len = sizeof (unbind_req);
	ctl.buf = (char *)&unbind_req;
	
	flags = 0;
	
	return putmsg (fd, &ctl, (struct strbuf*)NULL, flags);
}


/*
 * dlpidetachreq - send a request to detach.  This function is not actually
 *	used by ISC DHCP, but is included for completeness in case it is
 *	ever required for new work.
 */
static int dlpidetachreq (fd)
	int fd;
{
	dl_detach_req_t	detach_req;
	struct strbuf ctl;
	int flags;
	
	detach_req.dl_primitive = DL_DETACH_REQ;
	
	ctl.maxlen = 0;
	ctl.len = sizeof (detach_req);
	ctl.buf = (char *)&detach_req;
	
	flags = 0;
	
	return putmsg (fd, &ctl, (struct strbuf*)NULL, flags);
}
#endif /* UNUSED_DLPI_INTERFACE */


/*
 * dlpibindack - receive an ack to a dlbindreq.
 */
static int dlpibindack (fd, bufp)
	char *bufp;
	int fd;
{
	union DL_primitives *dlp;
	struct strbuf ctl;
	int flags;
	
	ctl.maxlen = DLPI_MAXDLBUF;
	ctl.len = 0;
	ctl.buf = bufp;

	if (strgetmsg (fd, &ctl,
		       (struct strbuf*)NULL, &flags, "dlpibindack") < 0) {
		return -1;
	}
	
	dlp = (union DL_primitives *)ctl.buf;
	
	if (!expected (DL_BIND_ACK, dlp, flags) < 0) {
		return -1;
	}
	
	if (ctl.len < sizeof (dl_bind_ack_t)) {
		/* Returned structure is too short */
		return -1;
	}

	return 0;
}

/*
 * dlpiokack - general acknowledgement reception.
 */
static int dlpiokack (fd, bufp)
	char *bufp;
	int fd;
{
	union DL_primitives *dlp;
	struct strbuf ctl;
	int flags;
	
	ctl.maxlen = DLPI_MAXDLBUF;
	ctl.len = 0;
	ctl.buf = bufp;
	
	if (strgetmsg (fd, &ctl,
		       (struct strbuf*)NULL, &flags, "dlpiokack") < 0) {
		return -1;
	}
	
	dlp = (union DL_primitives *)ctl.buf;
	
	if (!expected (DL_OK_ACK, dlp, flags) < 0) {
		return -1;
	}
	
	if (ctl.len < sizeof (dl_ok_ack_t)) {
		/* Returned structure is too short */
		return -1;
	}
	
	return 0;
}

/*
 * dlpiinfoack - receive an ack to a dlinforeq.
 */
static int dlpiinfoack (fd, bufp)
	char *bufp;
	int fd;
{
	union DL_primitives *dlp;
	struct strbuf ctl;
	int flags;
	
	ctl.maxlen = DLPI_MAXDLBUF;
	ctl.len = 0;
	ctl.buf = bufp;
	
	if (strgetmsg (fd, &ctl, (struct strbuf *)NULL, &flags,
		       "dlpiinfoack") < 0) {
		return -1;
	}
	
	dlp = (union DL_primitives *) ctl.buf;
	
	if (!expected (DL_INFO_ACK, dlp, flags) < 0) {
		return -1;
	}
	
	if (ctl.len < sizeof (dl_info_ack_t)) {
		/* Returned structure is too short */
		return -1;
	}
	
	return 0;
}

/*
 * dlpiphysaddrack - receive an ack to a dlpiphysaddrreq.
 */
int dlpiphysaddrack (fd, bufp)
	char *bufp;
	int fd;
{
	union DL_primitives *dlp;
	struct strbuf ctl;
	int flags;
	
	ctl.maxlen = DLPI_MAXDLBUF;
	ctl.len = 0;
	ctl.buf = bufp;
	
	if (strgetmsg (fd, &ctl, (struct strbuf *)NULL, &flags,
		       "dlpiphysaddrack") < 0) {
		return -1;
	}

	dlp = (union DL_primitives *)ctl.buf;
	
	if (!expected (DL_PHYS_ADDR_ACK, dlp, flags) < 0) {
		return -1;
	}

	if (ctl.len < sizeof (dl_phys_addr_ack_t)) {
		/* Returned structure is too short */
		return -1;
	}
	
	return 0;
}

#if defined(USE_DLPI_SEND) || defined(USE_DLPI_RECEIVE)
int dlpiunitdatareq (fd, addr, addrlen, minpri, maxpri, dbuf, dbuflen)
	int fd;
	unsigned char *addr;
	int addrlen;
	unsigned long minpri;
	unsigned long maxpri;
	unsigned char *dbuf;
	int dbuflen;
{
	long buf [DLPI_MAXDLBUF];
	union DL_primitives *dlp;
	struct strbuf ctl, data;
	
	/* Set up the control information... */
	dlp = (union DL_primitives *)buf;
	dlp -> unitdata_req.dl_primitive = DL_UNITDATA_REQ;
	dlp -> unitdata_req.dl_dest_addr_length = addrlen;
	dlp -> unitdata_req.dl_dest_addr_offset = sizeof (dl_unitdata_req_t);
	dlp -> unitdata_req.dl_priority.dl_min = minpri;
	dlp -> unitdata_req.dl_priority.dl_max = maxpri;

	/* Append the destination address */
	memcpy ((char *)buf + dlp -> unitdata_req.dl_dest_addr_offset,
		addr, addrlen);
	
	ctl.maxlen = 0;
	ctl.len = dlp -> unitdata_req.dl_dest_addr_offset + addrlen;
	ctl.buf = (char *)buf;

	data.maxlen = 0;
	data.buf = (char *)dbuf;
	data.len = dbuflen;

	/* Send the packet down the wire... */
	return putmsg (fd, &ctl, &data, 0);
}

static int dlpiunitdataind (fd, daddr, daddrlen,
			    saddr, saddrlen, grpaddr, dbuf, dlen)
	int fd;
	unsigned char *daddr;
	unsigned long *daddrlen;
	unsigned char *saddr;
	unsigned long *saddrlen;
	unsigned long *grpaddr;
	unsigned char *dbuf;
	int dlen;
{
	long buf [DLPI_MAXDLBUF];
	union DL_primitives *dlp;
	struct strbuf ctl, data;
	int flags = 0;
	int result;

	/* Set up the msg_buf structure... */
	dlp = (union DL_primitives *)buf;
	dlp -> unitdata_ind.dl_primitive = DL_UNITDATA_IND;

	ctl.maxlen = DLPI_MAXDLBUF;
	ctl.len = 0;
	ctl.buf = (char *)buf;

	data.maxlen = dlen;
	data.len = 0;
	data.buf = (char *)dbuf;

	result = getmsg (fd, &ctl, &data, &flags);

	/*
	 * The getmsg() manpage says:
	 *
	 * "On successful completion, a non-negative value is returned."
	 *
	 * This suggests that if MOREDATA or MORECTL are set, we error?
	 * This seems to be safe as it never seems to happen.  Still,
	 * set a log message, so we know if it ever starts happening.
	 */
	if (result != 0) {
		log_debug("dlpiunitdataind: %m");
		return -1;
	}

	if (ctl.len < sizeof (dl_unitdata_ind_t) ||
	    dlp -> unitdata_ind.dl_primitive != DL_UNITDATA_IND) {
		return -1;
	}

	if (data.len <= 0) {
		return data.len;
	}

	/* Copy sender info */
	if (saddr) {
		memcpy (saddr,
			(char *)buf + dlp -> unitdata_ind.dl_src_addr_offset,
			dlp -> unitdata_ind.dl_src_addr_length);
	}
	if (saddrlen) {
		*saddrlen = dlp -> unitdata_ind.dl_src_addr_length;
	}

	/* Copy destination info */
	if (daddr) {
		memcpy (daddr,
			(char *)buf + dlp -> unitdata_ind.dl_dest_addr_offset,
			dlp -> unitdata_ind.dl_dest_addr_length);
	}
	if (daddrlen) {
		*daddrlen = dlp -> unitdata_ind.dl_dest_addr_length;
	}

	if (grpaddr) {
		*grpaddr = dlp -> unitdata_ind.dl_group_address;
	}

	return data.len;
}
#endif /* !USE_DLPI_HWADDR: USE_DLPI_RECEIVE || USE_DLPI_SEND */

/*
 * expected - see if we got what we wanted.
 */
static int expected (prim, dlp, msgflags)
	unsigned long prim;
	union DL_primitives *dlp;
	int msgflags;
{
	if (msgflags != RS_HIPRI) {
		/* Message was not M_PCPROTO */
		return 0;
	}

	if (dlp -> dl_primitive != prim) {
		/* Incorrect/unexpected return message */
		return 0;
	}
	
	return 1;
}

/*
 * strgetmsg - get a message from a stream, with timeout.
 */
static int strgetmsg (fd, ctlp, datap, flagsp, caller)
	struct strbuf *ctlp, *datap;
	char *caller;
	int *flagsp;
	int fd;
{
	int result;
	struct pollfd pfd;
	int count;
	time_t now;
	time_t starttime;
	int to_msec;
	
	pfd.fd = fd;
	pfd.events = POLLPRI;	/* We're only interested in knowing
				 * when we can receive the next high
				 * priority message.
				 */
	pfd.revents = 0;

	now = time (&starttime);
	while (now <= starttime + DLPI_MAXWAIT) {
		to_msec = ((starttime + DLPI_MAXWAIT) - now) * 1000;
		count = poll (&pfd, 1, to_msec);
		
		if (count == 0) {
			/* log_fatal ("strgetmsg: timeout"); */
			return -1;
		} else if (count < 0) {
			if (errno == EAGAIN || errno == EINTR) {
				time (&now);
				continue;
			} else {
				/* log_fatal ("poll: %m"); */
				return -1;
			}
		} else {
			break;
		}
	}

	/*
	 * Set flags argument and issue getmsg ().
	 */
	*flagsp = 0;
	if ((result = getmsg (fd, ctlp, datap, flagsp)) < 0) {
		return result;
	}

	/*
	 * Check for MOREDATA and/or MORECTL.
	 */
	if (result & (MORECTL|MOREDATA)) {
		return -1;
	}

	/*
	 * Check for at least sizeof (long) control data portion.
	 */
	if (ctlp -> len < sizeof (long)) {
		return -1;
	}

	return 0;
}

#if defined(USE_DLPI_SEND)
int can_unicast_without_arp (ip)
	struct interface_info *ip;
{
	return 1;
}

int can_receive_unicast_unconfigured (ip)
	struct interface_info *ip;
{
	return 1;
}

int supports_multiple_interfaces (ip)
	struct interface_info *ip;
{
	return 1;
}

void maybe_setup_fallback ()
{
	isc_result_t status;
	struct interface_info *fbi = (struct interface_info *)0;
	if (setup_fallback (&fbi, MDL)) {
		if_register_fallback (fbi);
		status = omapi_register_io_object ((omapi_object_t *)fbi,
						   if_readsocket, 0,
						   fallback_discard, 0, 0);
		if (status != ISC_R_SUCCESS)
			log_fatal ("Can't register I/O handle for %s: %s",
				   fbi -> name, isc_result_totext (status));
		interface_dereference (&fbi, MDL);
	}
}
#endif /* USE_DLPI_SEND */

void 
get_hw_addr(const char *name, struct hardware *hw) {
	int sock, unit;
	long buf[DLPI_MAXDLBUF];
        union DL_primitives *dlp;

        dlp = (union DL_primitives *)buf;

	/* 
	 * Open a DLPI device.
	 */
	sock = dlpiopen(name);
	if (sock < 0) {
		log_fatal("Can't open DLPI device for %s: %m", name);
	}

	/*
	 * Submit a DL_INFO_REQ request, to find the dl_mac_type and 
         * dl_provider_style
	 */
	if (dlpiinforeq(sock) < 0) {
	    log_fatal("Can't request DLPI MAC type for %s: %m", name);
	}
	if (dlpiinfoack(sock, (char *)buf) < 0) {
	    log_fatal("Can't get DLPI MAC type for %s: %m", name);
	}
	switch (dlp->info_ack.dl_mac_type) {
		case DL_CSMACD: /* IEEE 802.3 */
		case DL_ETHER:
			hw->hbuf[0] = HTYPE_ETHER;
			break;
		case DL_TPR:
			hw->hbuf[0] = HTYPE_IEEE802;
			break;
		case DL_FDDI:
			hw->hbuf[0] = HTYPE_FDDI;
			break;
		default:
			log_fatal("%s: unsupported DLPI MAC type %lu", name,
				  (unsigned long)dlp->info_ack.dl_mac_type);
	}

	if (dlp->info_ack.dl_provider_style == DL_STYLE2) {
		/*
		 * Attach to the device.  If this fails, the device
		 * does not exist.
		 */
		unit = dlpiunit((char *)name);

		if (dlpiattachreq(sock, unit) < 0 ||
		    dlpiokack(sock, (char *)buf) < 0) {
			log_fatal("Can't attach DLPI device for %s: %m",
				  name);
		}
	}

	/*
	 * Submit a DL_PHYS_ADDR_REQ request, to find
	 * the hardware address.
	 */
	if (dlpiphysaddrreq(sock, DL_CURR_PHYS_ADDR) < 0) {
		log_fatal("Can't request DLPI hardware address for %s: %m",
			  name);
	}
	if (dlpiphysaddrack(sock, (char *)buf) < 0) {
		log_fatal("Can't get DLPI hardware address for %s: %m",
			  name);
	}
	if (dlp->physaddr_ack.dl_addr_length < sizeof(hw->hbuf)) {
		memcpy(hw->hbuf+1, 
		       (char *)buf + dlp->physaddr_ack.dl_addr_offset,
		       dlp->physaddr_ack.dl_addr_length);
		hw->hlen = dlp->physaddr_ack.dl_addr_length + 1;
	} else {
		memcpy(hw->hbuf+1, 
		       (char *)buf + dlp->physaddr_ack.dl_addr_offset,
		       sizeof(hw->hbuf)-1);
		hw->hlen = sizeof(hw->hbuf);
	}

	close(sock);
}
#endif /* USE_DLPI_SEND || USE_DLPI_RECEIVE || USE_DLPI_HWADDR */
