The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
#ifdef __cplusplus
extern "C" {
#endif
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <sys/socket.h>
#include <net/if.h>
#include <errno.h>

#ifdef __cplusplus
}
#endif

/*
** Fix up non-POSIX compliant address family names
*/

#ifndef AF_FILE
# ifdef AF_UNIX
#  define AF_FILE AF_UNIX
# else
#  ifdef AF_LOCAL
#   define AF_FILE AF_LOCAL
#  endif
# endif
#endif

/*
** Fix up slightly damaged ifreq entries
*/

#ifndef ifr_mtu
#define ifr_mtu ifr_ifru.ifru_metric
#endif

/*
** Function select values for int and sockaddr access routines
*/

enum _int_fields
{
  NI_FLAGS = 1, NI_MTU, NI_METRIC
};

enum _sockaddr_fields
{
  NI_ADDR = 1, NI_BRDADDR, NI_NETMASK, NI_HWADDR, NI_DSTADDR
};

/*
** Short-cuts to reduce typing
*/

typedef struct ifreq ifreq;

typedef union sockaddr_all {
  struct sockaddr sa;
  struct sockaddr_in sin;
} sockaddr_all;

#define NI_REF_CHECK(ref) \
	if (!SvROK (ref) || !SvOBJECT (SvRV(ref)) || !SvREADONLY (SvRV(ref))) \
          croak ("Can't call method \"%s\" without a valid object reference", GvNAME (CvGV (cv)));

#define NI_CONNECT(fd) \
	if ((fd = socket (PF_INET, SOCK_DGRAM, 0)) == -1) \
	  XSRETURN_EMPTY;

#define NI_ACCESS(fd,value,buf) \
	if (ioctl (fd, value, buf) == -1) { \
	  NI_DISCONNECT (fd); \
	  XSRETURN_EMPTY; \
	}

#define NI_DISCONNECT(fd) close (fd)

#define	NI_NEW_REF(rv,sv,stash) \
	sv = newSV (0); \
	rv = sv_2mortal (newRV_noinc (sv)); \
	sv_bless (rv, stash); \
	SvGROW (sv, sizeof (ifreq)); \
	SvREADONLY_on (sv); \
	XPUSHs (rv);

#define NI_MAX_ARGS(x) \
	if (items > x) \
	  croak ("Too many arguments for method \"%s\"", GvNAME (CvGV (cv)))

MODULE = Net::Interface	PACKAGE = Net::Interface

#sub ifa_broadaddr () { &ifa_ifu. &ifu_broadaddr;}
#sub ifa_dstaddr () { &ifa_ifu. &ifu_dstaddr;}

MODULE = Net::Interface	PACKAGE = Net::Interface

void
interfaces (ref)
  SV *ref;

  PROTOTYPE: $

  PREINIT:
    struct ifconf ifc;
    ifreq *ifr;
    int fd, n;
    HV *stash;
    SV *rv, *sv;

  PPCODE:
    {
      NI_CONNECT (fd);
#ifdef SIOCGIFCOUNT
      if (ioctl (fd, SIOCGIFCOUNT, &ifc) != -1) {
	New (0xbad, ifr, ifc.ifc_len, struct ifreq);
	ifc.ifc_req = ifr;
	ifc.ifc_len *= sizeof (ifreq);
	if (ioctl (fd, SIOCGIFCONF, &ifc) == -1) {
	  Safefree (ifr);
	  close (fd);
	  XSRETURN_EMPTY;
	}
      }
      else
#endif
	{
	  n = 3;
	  New (0xbad, ifr, n, ifreq);
	  do {
	    n *= 2;
	    Renew (ifr, n, ifreq);
	    ifc.ifc_req = ifr;
	    ifc.ifc_len = n * sizeof (ifreq);
	  }
	  while (ioctl (fd, SIOCGIFCONF, &ifc) == -1 || 
		 ifc.ifc_len == n * sizeof (ifreq));
	  NI_DISCONNECT (fd);
	}
      stash = SvROK (ref) ? SvSTASH (SvRV (ref)) : gv_stashsv (ref, 0);
      for (n = ifc.ifc_len / sizeof (ifreq); n; --n, ++ifr) {
	NI_NEW_REF (rv, sv, stash);
	Move (ifr, SvPVX (sv), 1, ifreq);
      }
      Safefree (ifc.ifc_req);
    }

void
new (...)

  PROTOTYPE: $$

  PREINIT:
    SV *rv, *sv;
    HV *stash;
    int fd;

  PPCODE:
    {
      NI_MAX_ARGS (2);
      stash = SvROK (ST (0)) ? SvSTASH (SvRV (ST (0))) : 
	gv_stashsv (ST (0), 0);
      NI_NEW_REF (rv, sv, stash);
      Move (SvPV (ST (1), na), ((ifreq *) SvPVX (sv))->ifr_name, 
	    SvCUR (ST (1)) + 1, char);
      NI_CONNECT (fd);
      NI_ACCESS (fd, SIOCGIFFLAGS, SvPVX (sv));
      NI_DISCONNECT (fd);
      XSRETURN (1);
    }

void
name (...)

  PROTOTYPE: $

  PPCODE:
    {
      NI_MAX_ARGS (1);
      NI_REF_CHECK (ST (0));
      XSRETURN_PV (SvPVX (SvRV (ST (0))));
    }

void
_int_value (...)

  PROTOTYPE: $;$

  PREINIT:
    int fd;
    ifreq *ifr;

  ALIAS:
    flags = NI_FLAGS
    mtu = NI_MTU
    metric = NI_METRIC

  PPCODE:
    {
      NI_MAX_ARGS (2);
      NI_REF_CHECK (ST (0));
      NI_CONNECT (fd);
      ifr = (ifreq *) SvPVX (SvRV (ST (0)));
      ST (0) = &sv_undef;
      switch (ix) {
      case NI_FLAGS:
	NI_ACCESS (fd, SIOCGIFFLAGS, ifr);
	XST_mIV (0, ifr->ifr_flags);
	break;

      case NI_MTU:
	NI_ACCESS (fd, SIOCGIFMTU, ifr);
	XST_mIV (0, ifr->ifr_mtu);
	break;

      case NI_METRIC:
	NI_ACCESS (fd, SIOCGIFMETRIC, ifr);
	XST_mIV (0, ifr->ifr_metric);
	break;
      }
      if (items == 2) {
	switch (ix) {
	case NI_FLAGS:
	  ifr->ifr_flags = SvIV (ST (1));
	  NI_ACCESS (fd, SIOCSIFFLAGS, ifr);
	  break;

	case NI_MTU:
	  ifr->ifr_mtu = SvIV (ST (1));
	  NI_ACCESS (fd, SIOCSIFMTU, ifr);
	  break;

	case NI_METRIC:
	  ifr->ifr_metric = SvIV (ST (1));
	  NI_ACCESS (fd, SIOCSIFMETRIC, ifr);
	  break;
	}
      }
      NI_DISCONNECT (fd);
      XSRETURN (1);
    }

void
_addr_value (...)

  PROTOTYPE: $;$

  PREINIT:
    int fd, array = 0;
    ifreq *ifr;
    sockaddr_all sa;

  ALIAS:
    address = NI_ADDR
    broadcast = NI_BRDADDR
    netmask = NI_NETMASK
    hwaddress = NI_HWADDR
    destination = NI_DSTADDR

  PPCODE:
    {
      NI_MAX_ARGS (2);
      NI_REF_CHECK (ST (0));
      NI_CONNECT (fd);
      ifr = (ifreq *) SvPVX (SvRV (ST (0)));
      ST (0) = &sv_undef;
      switch (ix) {
      case NI_ADDR:
	NI_ACCESS (fd, SIOCGIFADDR, ifr);
	break;

      case NI_BRDADDR:
	NI_ACCESS (fd, SIOCGIFBRDADDR, ifr);
	break;

      case NI_NETMASK:
	NI_ACCESS (fd, SIOCGIFNETMASK, ifr);
	break;
#ifdef SIOCGIFHWADDR
      case NI_HWADDR:
	NI_ACCESS (fd, SIOCGIFHWADDR, ifr);
	break;
#endif
      case NI_DSTADDR:
	NI_ACCESS (fd, SIOCGIFDSTADDR, ifr);
	break;
      }
      Move (&(ifr->ifr_addr), &sa, 1, sockaddr_all);
      if (items == 2) {
	switch (ix) {
	case NI_ADDR:
	  NI_ACCESS (fd, SIOCGIFADDR, ifr);
	  break;

	case NI_BRDADDR:
	  NI_ACCESS (fd, SIOCGIFBRDADDR, ifr);
	  break;

	case NI_NETMASK:
	  NI_ACCESS (fd, SIOCGIFNETMASK, ifr);
	  break;
#ifdef SIOCGIFHWADDR
	case NI_HWADDR:
	  NI_ACCESS (fd, SIOCGIFHWADDR, ifr);
	  break;
#endif
	case NI_DSTADDR:
	  NI_ACCESS (fd, SIOCGIFDSTADDR, ifr);
	  break;
	}
      }
      NI_DISCONNECT (fd);
      array = (GIMME_V == G_ARRAY);
      if (array) {
	EXTEND (sp, 2);
	PUSHs (sv_2mortal (newSViv (sa.sa.sa_family)));
      }
      switch (sa.sa.sa_family) {
      case AF_INET:
	if (array)
	  PUSHs (sv_2mortal (newSViv (sizeof (sa.sin.sin_addr))));
	PUSHs (sv_2mortal (newSVpv ((char *) &sa.sin.sin_addr,
				      sizeof (sa.sin.sin_addr))));
	break;

      case AF_FILE:
	if (array)
	  PUSHs (sv_2mortal (newSViv (sizeof (sa.sin.sin_addr))));
      }
    }

#sub ifr_map () { &ifr_ifru. &ifru_map;}
#sub ifr_slave () { &ifr_ifru. &ifru_slave;}
#sub ifr_data () { &ifr_ifru. &ifru_data;}
#sub ifc_buf () { &ifc_ifcu. &ifcu_buf;}
#sub ifc_req () { &ifc_ifcu. &ifcu_req;}