/* $Id: dlpi.c,v 1.3 2001/12/24 01:09:00 sybalsky Exp $ (C) Copyright Venue, All Rights Reserved  */

#include "os.h"

#ifdef USE_DLPI
/*
 * dpli.c - routines for messing with the Data Link Provider Interface.
 *
 * The code in this module is based in large part (especially the dl*
 * routines) on the example code provided with the document "How to Use
 * DLPI", by Neal Nuckolls of Sun Internet Engineering.  Gotta give credit
 * where credit is due.  If it weren't for Neal's excellent document,
 * this module just plain wouldn't exist.
 *
 * David A. Curry
 * Purdue University
 * Engineering Computer Network
 * 1285 Electrical Engineering Building
 * West Lafayette, IN 47907-1285
 * davy@ecn.purdue.edu
 *
 * $Log: dlpi.c,v $
 * Revision 1.3  2001/12/24 01:09:00  sybalsky
 * past changes
 *
 * Revision 1.2  1999/01/03 02:06:54  sybalsky
 * Add ID comments / static to files for CVS use
 *
 * Revision 1.1.1.1  1998/12/17 05:03:20  sybalsky
 * Import of Medley 3.5 emulator
 *
 * Revision 4.1  1993/09/15  20:50:44  davy
 * GCC fixes from Guy Harris.
 *
 * Revision 4.1  1993/09/15  20:50:44  davy
 * GCC fixes from Guy Harris.
 *
 * Revision 4.0  1993/03/01  19:59:00  davy
 * NFSWATCH Version 4.0.
 *
 * Revision 1.5  1993/02/19  19:54:36  davy
 * Another change in hopes of making things work on SVR4.
 *
 * Revision 1.4  1993/01/26  13:19:05  davy
 * Fixed a goof in passing buffer size.
 *
 * Revision 1.3  1993/01/26  13:18:39  davy
 * Added ifdef's to make it work on DLPI 1.3.
 *
 * Revision 1.2  1993/01/15  19:33:39  davy
 * Miscellaneous cleanups.
 *
 * Revision 1.1  1993/01/15  15:42:32  davy
 * Initial revision
 *
 */
#include <sys/param.h>
#include <sys/stropts.h>
#include <sys/stream.h>
#include <sys/dlpi.h>
#ifdef OS5
#include <sys/bufmod.h>
#include <unistd.h>
#include <stdlib.h>
#include <stropts.h>
#include <malloc.h>
#endif
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <net/if.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>

#include "nfswatch.h"

static void dlbindreq();
static void dlinforeq();
static void dlattachreq();
static void dlpromisconreq();
static void sigalrm();
static int dlokack();
static int dlinfoack();
static int dlbindack();
static int expecting();
static int strgetmsg();
static char *savestr();
static char *pname;

extern unsigned char ether_host[6];

int truncation = 1500;
/*
 * setup_dlpi_dev - set up the data link provider interface.
 */
int setup_dlpi_dev(char *device)
{
  char *p;
  u_int chunksz;
  char cbuf[BUFSIZ];
  struct ifconf ifc;
  struct ifreq *ifrp;
  struct strioctl si;
  char devname[BUFSIZ];
  int n, s, fd, devppa;
  struct timeval timeout;
  long buf[DLPI_MAXDLBUF];

  /*
   * If the interface device was not specified,
   * get the default one.
   */
  if ((device == NULL) || *device == NULL) {
    /*
     * Grab a socket.
     */
    if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
      perror("socket");
      return (-1);
    }

    ifc.ifc_buf = cbuf;
    ifc.ifc_len = sizeof(cbuf);

    /*
     * See what devices we've got.
     */
    if (ioctl(s, SIOCGIFCONF, (char *)&ifc) < 0) {
      perror("ioctl: SIOCGIFCONF");
      return (-1);
    }

    /*
     * Take the first device we encounter.
     */
    ifrp = ifc.ifc_req;
    for (n = ifc.ifc_len / sizeof(struct ifreq); n > 0; n--, ifrp++) {
      /*
       * Skip the loopback interface.
       */
      if (strcmp(ifrp->ifr_name, "lo0") == 0) continue;

      device = savestr(ifrp->ifr_name);
      break;
    }

    (void)close(s);
  }

  /*
   * Split the device name into type and unit number.
   */
  if ((p = strpbrk(device, "0123456789")) == NULL) return (-1);

  strcpy(devname, DLPI_DEVDIR);
  strncat(devname, device, p - device);
  devppa = atoi(p);

  /*
   * Open the device.
   */
  if ((fd = open(devname, O_RDWR)) < 0) {
    if (errno == ENOENT || errno == ENXIO) return (-1);

    /*perror(devname);*/
    return (-1);
  }

  /*
   * Attach to the device.  If this fails, the device
   * does not exist.
   */
  dlattachreq(fd, devppa);

  if (dlokack(fd, buf) < 0) {
    close(fd);
    return (-1);
  }

  /*
   * Bind to the specific unit.
   */
  dlbindreq(fd, 0x0600, 0, DL_CLDLS, 0, 0);

  if (dlbindack(fd, buf) < 0) {
    fprintf(stderr, "%s: dlbindack failed.\n", pname);
    return (-1);
  }

#ifdef OS5
  /*
   * We really want all types of packets.  However, the SVR4 DLPI does
   * not let you have the packet frame header, so we won't be able to
   * distinguish protocol types.  But SunOS5 gives you the DLIOCRAW
   * ioctl to get the frame headers, so we can do this on SunOS5.
   */
  dlpromisconreq(fd, DL_PROMISC_SAP);

  if (dlokack(fd, buf) < 0) {
    fprintf(stderr, "%s: DL_PROMISC_SAP failed.\n", pname);
    return (-1);
  }

  dlpromisconreq(fd, DL_PROMISC_MULTI);

  if (dlokack(fd, buf) < 0) {
    fprintf(stderr, "%s: DL_PROMISC_MULTI failed.\n", pname);
    return (-1);
  }

  /*
   * We want raw packets with the packet frame header.  But we can
   * only get this in SunOS5 with the DLIOCRAW ioctl; it's not in
   * standard SVR4.
   */
  si.ic_cmd = DLIOCRAW;
  si.ic_timout = -1;
  si.ic_len = 0;
  si.ic_dp = 0;

  if (ioctl(fd, I_STR, &si) < 0) {
    perror("ioctl: I_STR DLIOCRAW");
    return (-1);
  }
#endif /* OS5 */

  /*
   * Arrange to get discrete messages.
   */
  if (ioctl(fd, I_SRDOPT, (char *)RMSGD) < 0) {
    perror("ioctl: I_SRDOPT RMSGD");
    return (-1);
  }

#ifdef OS5
/*
 * Push and configure the streams buffering module.  This is once
 * again SunOS-specific.
 */
#ifdef NEVER
  if (ioctl(fd, I_PUSH, DLPI_BUFMOD) < 0) {
    perror("ioctl: I_PUSH BUFMOD");
    return (-1);
  }
  /*
   * Set the read timeout.
   */
  timeout.tv_sec = 1;
  timeout.tv_usec = 0;

  si.ic_cmd = SBIOCSTIME;
  si.ic_timout = INFTIM;
  si.ic_len = sizeof(timeout);
  si.ic_dp = (char *)&timeout;

  if (ioctl(fd, I_STR, (char *)&si) < 0) {
    perror("ioctl: I_STR SBIOCSTIME");
    return (-1);
  }

  /*
   * Set the chunk size.
   */
  chunksz = DLPI_CHUNKSIZE;

  si.ic_cmd = SBIOCSCHUNK;
  si.ic_len = sizeof(chunksz);
  si.ic_dp = (char *)&chunksz;

  if (ioctl(fd, I_STR, (char *)&si) < 0) {
    perror("ioctl: I_STR SBIOCSCHUNK");
    return (-1);
  }

  /*
   * Set snapshot mode.
   */
  si.ic_cmd = SBIOCSSNAP;
  si.ic_len = sizeof(truncation);
  si.ic_dp = (char *)&truncation;

  if (ioctl(fd, I_STR, (char *)&si) < 0) {
    perror("ioctl: I_STR SBIOCSSNAP");
    return (-1);
  }
#endif /* NEVER */

#endif /* OS5 */

  return (fd);
}

/*
 * flush_dlpi - flush data from the dlpi.
 */
void flush_dlpi(int fd)
{
  if (ioctl(fd, I_FLUSH, (char *)FLUSHR) < 0) {
    perror("ioctl: I_FLUSH");
    return;
  }
}

/*
 * dlpi_devtype - determine the type of device we're looking at.
 */
int dlpi_devtype(int fd)
{
  long buf[DLPI_MAXDLBUF];
  union DL_primitives *dlp;

  dlp = (union DL_primitives *)buf;

  dlinforeq(fd);

  if (dlinfoack(fd, buf) < 0) return (DLT_EN10MB);

  bcopy((char *)dlp + dlp->info_ack.dl_addr_offset, ether_host, 6);

  switch (dlp->info_ack.dl_mac_type) {
    case DL_CSMACD:
    case DL_ETHER: return (DLT_EN10MB);
#ifdef DL_FDDI
    case DL_FDDI: return (DLT_FDDI);
#endif
    default:
      fprintf(stderr, "%s: DLPI MACtype %d unknown, ", pname, dlp->info_ack.dl_mac_type);
      fprintf(stderr, "assuming ethernet.\n");
      return (DLT_EN10MB);
  }
}

/*
 * dlinforeq - request information about the data link provider.
 */
static void dlinforeq(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;

  if (putmsg(fd, &ctl, (struct strbuf *)NULL, flags) < 0) {
    perror("putmsg");
    return;
  }
}

/*
 * dlattachreq - send a request to attach.
 */
static void dlattachreq(int fd, u_long ppa)
{
  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;

  if (putmsg(fd, &ctl, (struct strbuf *)NULL, flags) < 0) {
    perror("putmsg");
    return;
  }
}

#ifdef DL_PROMISCON_REQ
/*
 * dlpromisconreq - send a request to turn promiscuous mode on.
 */
static void dlpromisconreq(int fd, u_long level)
{
  dl_promiscon_req_t promiscon_req;
  struct strbuf ctl;
  int flags;

  promiscon_req.dl_primitive = DL_PROMISCON_REQ;
  promiscon_req.dl_level = level;

  ctl.maxlen = 0;
  ctl.len = sizeof(promiscon_req);
  ctl.buf = (char *)&promiscon_req;

  flags = 0;

  if (putmsg(fd, &ctl, (struct strbuf *)NULL, flags) < 0) {
    perror("putmsg");
    return;
  }
}
#endif /* DL_PROMISCON_REQ */

/*
 * dlbindreq - send a request to bind.
 */
static void dlbindreq(int fd, u_long sap, u_long max_conind, u_long service_mode, u_long conn_mgmt, u_long xidtest)
{
  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;
#ifdef DL_PROMISC_PHYS
  /*
   * DLPI 2.0 only?
   */
  bind_req.dl_xidtest_flg = xidtest;
#endif

  ctl.maxlen = 0;
  ctl.len = sizeof(bind_req);
  ctl.buf = (char *)&bind_req;

  flags = 0;

  if (putmsg(fd, &ctl, (struct strbuf *)NULL, flags) < 0) {
    perror("putmsg");
    return;
  }
}

/*
 * dlokack - general acknowledgement reception.
 */
static int dlokack(int fd, char *bufp)
{
  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, "dlokack") < 0) return (-1);

  dlp = (union DL_primitives *)ctl.buf;

  if (expecting(DL_OK_ACK, dlp) < 0) return (-1);

  if (ctl.len < sizeof(dl_ok_ack_t)) return (-1);

  if (flags != RS_HIPRI) return (-1);

  if (ctl.len < sizeof(dl_ok_ack_t)) return (-1);

  return (0);
}

/*
 * dlinfoack - receive an ack to a dlinforeq.
 */
static int dlinfoack(int fd, char *bufp)
{
  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, "dlinfoack") < 0) return (-1);

  dlp = (union DL_primitives *)ctl.buf;

  if (expecting(DL_INFO_ACK, dlp) < 0) return (-1);

  if (ctl.len < sizeof(dl_info_ack_t)) return (-1);

  if (flags != RS_HIPRI) return (-1);

  if (ctl.len < sizeof(dl_info_ack_t)) return (-1);

  return (0);
}

/*
 * dlbindack - receive an ack to a dlbindreq.
 */
static int dlbindack(int fd, char *bufp)
{
  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, "dlbindack") < 0) return (-1);

  dlp = (union DL_primitives *)ctl.buf;

  if (expecting(DL_BIND_ACK, dlp) < 0) return (-1);

  if (flags != RS_HIPRI) return (-1);

  if (ctl.len < sizeof(dl_bind_ack_t)) return (-1);

  return (0);
}

/*
 * expecting - see if we got what we wanted.
 */
static int expecting(int prim, union DL_primitives *dlp)
{
  if (dlp->dl_primitive != (u_long)prim) return (-1);

  return (0);
}

/*
 * strgetmsg - get a message from a stream, with timeout.
 */
static int strgetmsg(int fd, struct strbuf *ctlp, struct strbuf *datap, int *flagsp, char *caller)
{
  int rc;
  void sigalrm();

  /*
   * Start timer.
   */
  (void)sigset(SIGALRM, sigalrm);

  if (alarm(DLPI_MAXWAIT) < 0) {
    perror("alarm");
    return (-1);
  }

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

  /*
   * Stop timer.
   */
  if (alarm(0) < 0) {
    perror("alarm");
    return (-1);
  }

  /*
   * Check for MOREDATA and/or MORECTL.
   */
  if ((rc & (MORECTL | MOREDATA)) == (MORECTL | MOREDATA)) return (-1);
  if (rc & MORECTL) return (-1);
  if (rc & MOREDATA) return (-1);

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

  return (0);
}

/*
 * sigalrm - handle alarms.
 */
static void sigalrm() { (void)fprintf(stderr, "dlpi: timeout\n"); }

/*
 * savestr - save string in dynamic memory.
 */
char *savestr(char *s)
{
  char *t;

  if ((t = malloc(strlen(s) + 1)) == NULL) {
    (void)fprintf(stderr, "%s: out of memory.\n", pname);
    (void)exit(1);
  }

  (void)strcpy(t, s);

  return (t);
}

int dlunitdatareq(int fd, u_char *addrp, int addrlen, u_long minpri, u_long maxpri, u_char *datap, int datalen)
{
  long buf[DLPI_MAXDLBUF];
  union DL_primitives *dlp;
  struct strbuf data, ctl;

  dlp = (union DL_primitives *)buf;

  dlp->unitdata_req.dl_primitive = DL_UNITDATA_REQ;
  dlp->unitdata_req.dl_dest_addr_length = addrlen + 2;
  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;

  (void)memcpy(OFFADDR(dlp, sizeof(dl_unitdata_req_t)), addrp, addrlen);
  (void)memcpy(OFFADDR(dlp, sizeof(dl_unitdata_req_t) + addrlen), (char *)addrp + 12, 2);

  ctl.maxlen = 0;
  ctl.len = sizeof(dl_unitdata_req_t) + addrlen + 2;
  ctl.buf = (char *)buf;

  data.maxlen = 0;
  data.len = datalen;
  data.buf = (char *)datap;

#ifdef NEVER
  if (putmsg(fd, &ctl, &data, 0) < 0)
#else
  if (putmsg(fd, NULL, &data, 0) < 0)
#endif /* NEVER tst on 9/30/96 jds to see if raw out works */
    return -1;
  else
    return 0;
}
#endif /* USE_DLPI */
