/***********************************************************************
*
* if.c
*
* Implementation of user-space PPPoE redirector for Linux.
*
* Functions for opening a raw socket and reading/writing raw Ethernet frames.
*
* Copyright (C) 2000 by Roaring Penguin Software Inc.
*
* This program may be distributed according to the terms of the GNU
* General Public License, version 2 or (at your option) any later version.
*
***********************************************************************/
static char const RCSID[] =
"$Id: if.c,v 1.1 2001/12/14 02:55:20 mostrows Exp $";
#include "pppoe.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_NETPACKET_PACKET_H
#include <netpacket/packet.h>
#elif defined(HAVE_LINUX_IF_PACKET_H)
#include <linux/if_packet.h>
#endif
#ifdef HAVE_NET_ETHERNET_H
#include <net/ethernet.h>
#endif
#ifdef HAVE_ASM_TYPES_H
#include <asm/types.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#endif
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_NET_IF_ARP_H
#include <net/if_arp.h>
#endif
#ifdef USE_DLPI
#include <limits.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/dlpi.h>
#include <sys/bufmod.h>
#include <stdio.h>
#include <signal.h>
#include <stropts.h>
/* function declarations */
void dlpromisconreq( int fd, u_long level);
void dlinforeq(int fd);
void dlunitdatareq(int fd, u_char *addrp, int addrlen, u_long minpri, u_long maxpri, u_char *datap, int datalen);
void dlinfoack(int fd, char *bufp);
void dlbindreq(int fd, u_long sap, u_long max_conind, u_long service_mode, u_long conn_mgmt, u_long xidtest);
void dlattachreq(int fd, u_long ppa);
void dlokack(int fd, char *bufp);
void dlbindack(int fd, char *bufp);
int strioctl(int fd, int cmd, int timout, int len, char *dp);
void strgetmsg(int fd, struct strbuf *ctlp, struct strbuf *datap, int *flagsp, char *caller);
void sigalrm(int sig);
void expecting(int prim, union DL_primitives *dlp);
char *dlprim(u_long prim);
/* #define DL_DEBUG */
static int dl_abssaplen;
static int dl_saplen;
static int dl_addrlen;
#endif
#ifdef USE_BPF
#include <net/bpf.h>
#include <fcntl.h>
unsigned char *bpfBuffer; /* Packet filter buffer */
int bpfLength = 0; /* Packet filter buffer length */
int bpfSize = 0; /* Number of unread bytes in buffer */
int bpfOffset = 0; /* Current offset in bpfBuffer */
#endif
/* Initialize frame types to RFC 2516 values. Some broken peers apparently
use different frame types... sigh... */
UINT16_t Eth_PPPOE_Discovery = ETH_PPPOE_DISCOVERY;
UINT16_t Eth_PPPOE_Session = ETH_PPPOE_SESSION;
/**********************************************************************
*%FUNCTION: etherType
*%ARGUMENTS:
* packet -- a received PPPoE packet
*%RETURNS:
* ethernet packet type (see /usr/include/net/ethertypes.h)
*%DESCRIPTION:
* Checks the ethernet packet header to determine its type.
* We should only be receveing DISCOVERY and SESSION types if the BPF
* is set up correctly. Logs an error if an unexpected type is received.
* Note that the ethernet type names come from "pppoe.h" and the packet
* packet structure names use the LINUX dialect to maintain consistency
* with the rest of this file. See the BSD section of "pppoe.h" for
* translations of the data structure names.
***********************************************************************/
UINT16_t
etherType(PPPoEPacket *packet)
{
UINT16_t type = (UINT16_t) ntohs(packet->ethHdr.h_proto);
if (type != Eth_PPPOE_Discovery && type != Eth_PPPOE_Session) {
syslog(LOG_ERR, "Invalid ether type 0x%x", type);
}
return type;
}
#ifdef USE_BPF
/**********************************************************************
*%FUNCTION: getHWaddr
*%ARGUMENTS:
* ifname -- name of interface
* hwaddr -- buffer for ehthernet address
*%RETURNS:
* Nothing
*%DESCRIPTION:
* Locates the Ethernet hardware address for an interface.
***********************************************************************/
void
getHWaddr(int sock, char const *ifname, unsigned char *hwaddr)
{
char inbuf[8192];
const struct sockaddr_dl *sdl;
struct ifconf ifc;
struct ifreq ifreq, *ifr;
int i;
int found = 0;
ifc.ifc_len = sizeof(inbuf);
ifc.ifc_buf = inbuf;
if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) {
fatalSys("SIOCGIFCONF");
}
ifr = ifc.ifc_req;
ifreq.ifr_name[0] = '\0';
for (i = 0; i < ifc.ifc_len; ) {
ifr = (struct ifreq *)((caddr_t)ifc.ifc_req + i);
i += sizeof(ifr->ifr_name) +
(ifr->ifr_addr.sa_len > sizeof(struct sockaddr)
? ifr->ifr_addr.sa_len
: sizeof(struct sockaddr));
if (ifr->ifr_addr.sa_family == AF_LINK) {
sdl = (const struct sockaddr_dl *) &ifr->ifr_addr;
if ((sdl->sdl_type == IFT_ETHER) &&
(sdl->sdl_alen == ETH_ALEN) &&
!strncmp(ifname, ifr->ifr_name, sizeof(ifr->ifr_name))) {
if (found) {
char buffer[256];
sprintf(buffer, "interface %.16s has more than one ethernet address", ifname);
rp_fatal(buffer);
} else {
found = 1;
memcpy(hwaddr, LLADDR(sdl), ETH_ALEN);
}
}
}
}
if (!found) {
char buffer[256];
sprintf(buffer, "interface %.16s has no ethernet address", ifname);
rp_fatal(buffer);
}
}
/**********************************************************************
*%FUNCTION: initFilter
*%ARGUMENTS:
* fd -- file descriptor of BSD device
* type -- Ethernet frame type (0 for watch mode)
* hwaddr -- buffer with ehthernet address
*%RETURNS:
* Nothing
*%DESCRIPTION:
* Initializes the packet filter rules.
***********************************************************************/
void
initFilter(int fd, UINT16_t type, unsigned char *hwaddr)
{
/* Packet Filter Instructions:
* Note that the ethernet type names come from "pppoe.h" and are
* used here to maintain consistency with the rest of this file. */
static struct bpf_insn bpfRun[] = { /* run PPPoE */
BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12), /* ethernet type */
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETH_PPPOE_SESSION, 5, 0),
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETH_PPPOE_DISCOVERY, 0, 9),
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, 0), /* first word of dest. addr */
#define PPPOE_BCAST_CMPW 4 /* offset of word compare */
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0, 0, 2),
BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 4), /* next 1/2 word of dest. */
#define PPPOE_BCAST_CMPH 6 /* offset of 1/2 word compare */
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0, 4, 0),
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, 0), /* first word of dest. addr */
#define PPPOE_FILTER_CMPW 8 /* offset of word compare */
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0, 0, 3),
BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 4), /* next 1/2 word of dest. */
#define PPPOE_FILTER_CMPH 10 /* offset of 1/rd compare */
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0, 0, 1),
BPF_STMT(BPF_RET+BPF_K, (u_int) -1), /* keep packet */
BPF_STMT(BPF_RET+BPF_K, 0), /* drop packet */
};
/* Fix the potentially varying parts */
bpfRun[1].code = (u_short) BPF_JMP+BPF_JEQ+BPF_K;
bpfRun[1].jt = 5;
bpfRun[1].jf = 0;
bpfRun[1].k = Eth_PPPOE_Session;
bpfRun[2].code = (u_short) BPF_JMP+BPF_JEQ+BPF_K;
bpfRun[2].jt = 0;
bpfRun[2].jf = 9;
bpfRun[2].k = Eth_PPPOE_Discovery;
{
struct bpf_insn bpfInsn[sizeof(bpfRun) / sizeof(bpfRun[0])];
struct bpf_program bpfProgram;
memcpy(bpfInsn, bpfRun, sizeof(bpfRun));
bpfInsn[PPPOE_BCAST_CMPW].k = ((0xff << 24) | (0xff << 16) |
(0xff << 8) | 0xff);
bpfInsn[PPPOE_BCAST_CMPH].k = ((0xff << 8) | 0xff);
bpfInsn[PPPOE_FILTER_CMPW].k = ((hwaddr[0] << 24) | (hwaddr[1] << 16) |
(hwaddr[2] << 8) | hwaddr[3]);
bpfInsn[PPPOE_FILTER_CMPH].k = ((hwaddr[4] << 8) | hwaddr[5]);
bpfProgram.bf_len = (sizeof(bpfInsn) / sizeof(bpfInsn[0]));
bpfProgram.bf_insns = &bpfInsn[0];
/* Apply the filter */
if (ioctl(fd, BIOCSETF, &bpfProgram) < 0) {
fatalSys("ioctl(BIOCSETF)");
}
}
}
/**********************************************************************
*%FUNCTION: openInterface
*%ARGUMENTS:
* ifname -- name of interface
* type -- Ethernet frame type (0 for any frame type)
* hwaddr -- if non-NULL, set to the hardware address
*%RETURNS:
* A file descriptor for talking with the Ethernet card. Exits on error.
* Note that the Linux version of this routine returns a socket instead.
*%DESCRIPTION:
* Opens a BPF on an interface for all PPPoE traffic (discovery and
* session). If 'type' is 0, uses promiscuous mode to watch any PPPoE
* traffic on this network.
***********************************************************************/
int
openInterface(char const *ifname, UINT16_t type, unsigned char *hwaddr)
{
static int fd = -1;
char bpfName[32];
u_int optval;
struct bpf_version bpf_ver;
struct ifreq ifr;
int sock;
int i;
/* BSD only opens one socket for both Discovery and Session packets */
if (fd >= 0) {
return fd;
}
/* Find a free BPF device */
for (i = 0; i < 256; i++) {
sprintf(bpfName, "/dev/bpf%d", i);
if (((fd = open(bpfName, O_RDWR, 0)) >= 0) ||
(errno != EBUSY)) {
break;
}
}
if (fd < 0) {
switch (errno) {
case EACCES: /* permission denied */
{
char buffer[256];
sprintf(buffer, "Cannot open %.32s -- pppoe must be run as root.", bpfName);
rp_fatal(buffer);
}
break;
case EBUSY:
case ENOENT: /* no such file */
if (i == 0) {
rp_fatal("No /dev/bpf* devices (check your kernel configuration for BPF support)");
} else {
rp_fatal("All /dev/bpf* devices are in use");
}
break;
}
fatalSys(bpfName);
}
if ((sock = socket(AF_LOCAL, SOCK_DGRAM, 0)) < 0) {
fatalSys("socket");
}
/* Check that the interface is up */
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
fatalSys("ioctl(SIOCGIFFLAGS)");
}
if ((ifr.ifr_flags & IFF_UP) == 0) {
char buffer[256];
sprintf(buffer, "Interface %.16s is not up\n", ifname);
rp_fatal(buffer);
}
/* Fill in hardware address and initialize the packet filter rules */
if (hwaddr == NULL) {
rp_fatal("openInterface: no hwaddr arg.");
}
getHWaddr(sock, ifname, hwaddr);
initFilter(fd, type, hwaddr);
/* Sanity check on MTU -- apparently does not work on OpenBSD */
#if !defined(__OpenBSD__)
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
if (ioctl(sock, SIOCGIFMTU, &ifr) < 0) {
fatalSys("ioctl(SIOCGIFMTU)");
}
if (ifr.ifr_mtu < ETH_DATA_LEN) {
char buffer[256];
sprintf(buffer, "Interface %.16s has MTU of %d -- should be %d. You may have serious connection problems.",
ifname, ifr.ifr_mtu, ETH_DATA_LEN);
printErr(buffer);
}
#endif
/* done with the socket */
if (close(sock) < 0) {
fatalSys("close");
}
/* Check the BPF version number */
if (ioctl(fd, BIOCVERSION, &bpf_ver) < 0) {
fatalSys("ioctl(BIOCVERSION)");
}
if ((bpf_ver.bv_major != BPF_MAJOR_VERSION) ||
(bpf_ver.bv_minor < BPF_MINOR_VERSION)) {
char buffer[256];
sprintf(buffer, "Unsupported BPF version: %d.%d (kernel: %d.%d)",
BPF_MAJOR_VERSION, BPF_MINOR_VERSION,
bpf_ver.bv_major, bpf_ver.bv_minor);
rp_fatal(buffer);
}
/* allocate a receive packet buffer */
if (ioctl(fd, BIOCGBLEN, &bpfLength) < 0) {
fatalSys("ioctl(BIOCGBLEN)");
}
if (!(bpfBuffer = (unsigned char *) malloc(bpfLength))) {
rp_fatal("malloc");
}
/* reads should return as soon as there is a packet available */
optval = 1;
if (ioctl(fd, BIOCIMMEDIATE, &optval) < 0) {
fatalSys("ioctl(BIOCIMMEDIATE)");
}
/* Bind the interface to the filter */
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
if (ioctl(fd, BIOCSETIF, &ifr) < 0) {
char buffer[256];
sprintf(buffer, "ioctl(BIOCSETIF) can't select interface %.16s",
ifname);
rp_fatal(buffer);
}
syslog(LOG_INFO, "Interface=%.16s HWaddr=%02X:%02X:%02X:%02X:%02X:%02X Device=%.32s Buffer size=%d",
ifname,
hwaddr[0], hwaddr[1], hwaddr[2],
hwaddr[3], hwaddr[4], hwaddr[5],
bpfName, bpfLength);
return fd;
}
#endif /* USE_BPF */
#ifdef USE_LINUX_PACKET
/**********************************************************************
*%FUNCTION: openInterface
*%ARGUMENTS:
* ifname -- name of interface
* type -- Ethernet frame type
* hwaddr -- if non-NULL, set to the hardware address
*%RETURNS:
* A raw socket for talking to the Ethernet card. Exits on error.
*%DESCRIPTION:
* Opens a raw Ethernet socket
***********************************************************************/
int
openInterface(char const *ifname, UINT16_t type, unsigned char *hwaddr)
{
int optval=1;
int fd;
struct ifreq ifr;
int domain, stype;
#ifdef HAVE_STRUCT_SOCKADDR_LL
struct sockaddr_ll sa;
#else
struct sockaddr sa;
#endif
memset(&sa, 0, sizeof(sa));
#ifdef HAVE_STRUCT_SOCKADDR_LL
domain = PF_PACKET;
stype = SOCK_RAW;
#else
domain = PF_INET;
stype = SOCK_PACKET;
#endif
if ((fd = socket(domain, stype, htons(type))) < 0) {
/* Give a more helpful message for the common error case */
if (errno == EPERM) {
rp_fatal("Cannot create raw socket -- pppoe must be run as root.");
}
fatalSys("socket");
}
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)) < 0) {
fatalSys("setsockopt");
}
/* Fill in hardware address */
if (hwaddr) {
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
fatalSys("ioctl(SIOCGIFHWADDR)");
}
memcpy(hwaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
#ifdef ARPHRD_ETHER
if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) {
char buffer[256];
sprintf(buffer, "Interface %.16s is not Ethernet", ifname);
rp_fatal(buffer);
}
#endif
if (NOT_UNICAST(hwaddr)) {
char buffer[256];
sprintf(buffer,
"Interface %.16s has broadcast/multicast MAC address??",
ifname);
rp_fatal(buffer);
}
}
/* Sanity check on MTU */
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
if (ioctl(fd, SIOCGIFMTU, &ifr) < 0) {
fatalSys("ioctl(SIOCGIFMTU)");
}
if (ifr.ifr_mtu < ETH_DATA_LEN) {
char buffer[256];
sprintf(buffer, "Interface %.16s has MTU of %d -- should be %d. You may have serious connection problems.",
ifname, ifr.ifr_mtu, ETH_DATA_LEN);
printErr(buffer);
}
#ifdef HAVE_STRUCT_SOCKADDR_LL
/* Get interface index */
sa.sll_family = AF_PACKET;
sa.sll_protocol = htons(type);
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) {
fatalSys("ioctl(SIOCFIGINDEX): Could not get interface index");
}
sa.sll_ifindex = ifr.ifr_ifindex;
#else
strcpy(sa.sa_data, ifname);
#endif
/* We're only interested in packets on specified interface */
if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
fatalSys("bind");
}
return fd;
}
#endif /* USE_LINUX */
/***********************************************************************
*%FUNCTION: sendPacket
*%ARGUMENTS:
* sock -- socket to send to
* pkt -- the packet to transmit
* size -- size of packet (in bytes)
*%RETURNS:
* 0 on success; -1 on failure
*%DESCRIPTION:
* Transmits a packet
***********************************************************************/
int
sendPacket(PPPoEConnection *conn, int sock, PPPoEPacket *pkt, int size)
{
#if defined(USE_BPF)
if (write(sock, pkt, size) < 0) {
sysErr("write (sendPacket)");
return -1;
}
#elif defined(HAVE_STRUCT_SOCKADDR_LL)
if (send(sock, pkt, size, 0) < 0) {
sysErr("send (sendPacket)");
return -1;
}
#else
#ifdef USE_DLPI
#define ABS(x) ((x) < 0 ? -(x) : (x))
u_char addr[MAXDLADDR];
u_char phys[MAXDLADDR];
u_char sap[MAXDLADDR];
u_char xmitbuf[MAXDLBUF];
int data_size;
short tmp_sap;
tmp_sap = htons(pkt->ethHdr.h_proto);
data_size = size - sizeof(struct ethhdr);
memcpy((char *)phys, (char *)pkt->ethHdr.h_dest, ETHERADDRL);
memcpy((char *)sap, (char *)&tmp_sap, sizeof(ushort_t));
memcpy((char *)xmitbuf, (char *)pkt + sizeof(struct ethhdr), data_size);
if (dl_saplen > 0) { /* order is sap+phys */
(void) memcpy((char*)addr, (char*)&sap, dl_abssaplen);
(void) memcpy((char*)addr+dl_abssaplen, (char*)phys, ETHERADDRL);
} else { /* order is phys+sap */
(void) memcpy((char*)addr, (char*)phys, ETHERADDRL);
(void) memcpy((char*)addr+ETHERADDRL, (char*)&sap, dl_abssaplen);
}
#ifdef DL_DEBUG
printf("%02x:%02x:%02x:%02x:%02x:%02x %02x:%02x\n",
addr[0],addr[1],addr[2],addr[3],addr[4],addr[5],
addr[6],addr[7]);
#endif
dlunitdatareq(sock, addr, dl_addrlen, 0, 0, xmitbuf, data_size);
#else
struct sockaddr sa;
if (!conn) {
rp_fatal("relay and server not supported on Linux 2.0 kernels");
}
strcpy(sa.sa_data, conn->ifName);
if (sendto(sock, pkt, size, 0, &sa, sizeof(sa)) < 0) {
sysErr("sendto (sendPacket)");
return -1;
}
#endif
#endif
return 0;
}
#ifdef USE_BPF
/***********************************************************************
*%FUNCTION: clearPacketHeader
*%ARGUMENTS:
* pkt -- packet that needs its head clearing
*%RETURNS:
* nothing
*%DESCRIPTION:
* Clears a PPPoE packet header after a truncated packet has been
* received. Insures that the packet will fail any integrity tests
* and will be discarded by upper level routines. Also resets the
* bpfSize and bpfOffset variables to force a new read on the next
* call to receivePacket().
***********************************************************************/
void
clearPacketHeader(PPPoEPacket *pkt)
{
bpfSize = bpfOffset = 0;
memset(pkt, 0, HDR_SIZE);
}
#endif
/***********************************************************************
*%FUNCTION: receivePacket
*%ARGUMENTS:
* sock -- socket to read from
* pkt -- place to store the received packet
* size -- set to size of packet in bytes
*%RETURNS:
* >= 0 if all OK; < 0 if error
*%DESCRIPTION:
* Receives a packet
***********************************************************************/
int
receivePacket(int sock, PPPoEPacket *pkt, int *size)
{
#ifdef USE_BPF
struct bpf_hdr hdr;
int seglen, copylen;
if (bpfSize <= 0) {
bpfOffset = 0;
if ((bpfSize = read(sock, bpfBuffer, bpfLength)) < 0) {
sysErr("read (receivePacket)");
return -1;
}
}
if (bpfSize < sizeof(hdr)) {
syslog(LOG_ERR, "Truncated bpf packet header: len=%d", bpfSize);
clearPacketHeader(pkt); /* resets bpfSize and bpfOffset */
return 0;
}
memcpy(&hdr, bpfBuffer + bpfOffset, sizeof(hdr));
if (hdr.bh_caplen != hdr.bh_datalen) {
syslog(LOG_ERR, "Truncated bpf packet: caplen=%d, datalen=%d",
hdr.bh_caplen, hdr.bh_datalen);
clearPacketHeader(pkt); /* resets bpfSize and bpfOffset */
return 0;
}
seglen = hdr.bh_hdrlen + hdr.bh_caplen;
if (seglen > bpfSize) {
syslog(LOG_ERR, "Truncated bpf packet: seglen=%d, bpfSize=%d",
seglen, bpfSize);
clearPacketHeader(pkt); /* resets bpfSize and bpfOffset */
return 0;
}
seglen = BPF_WORDALIGN(seglen);
*size = copylen = ((hdr.bh_caplen < sizeof(PPPoEPacket)) ?
hdr.bh_caplen : sizeof(PPPoEPacket));
memcpy(pkt, bpfBuffer + bpfOffset + hdr.bh_hdrlen, copylen);
if (seglen >= bpfSize) {
bpfSize = bpfOffset = 0;
} else {
bpfSize -= seglen;
bpfOffset += seglen;
}
#else
#ifdef USE_DLPI
struct strbuf data;
int flags = 0;
int retval;
data.buf = (char *) pkt;
data.maxlen = MAXDLBUF;
data.len = 0;
if ((retval = getmsg(sock, NULL, &data, &flags)) < 0) {
sysErr("read (receivePacket)");
return -1;
}
*size = data.len;
#else
if ((*size = recv(sock, pkt, sizeof(PPPoEPacket), 0)) < 0) {
sysErr("recv (receivePacket)");
return -1;
}
#endif
#endif
return 0;
}
#ifdef USE_DLPI
/**********************************************************************
*%FUNCTION: openInterface
*%ARGUMENTS:
* ifname -- name of interface
* type -- Ethernet frame type
* hwaddr -- if non-NULL, set to the hardware address
*%RETURNS:
* A raw socket for talking to the Ethernet card. Exits on error.
*%DESCRIPTION:
* Opens a raw Ethernet socket
***********************************************************************/
int
openInterface(char const *ifname, UINT16_t type, unsigned char *hwaddr)
{
int fd;
long buf[MAXDLBUF];
union DL_primitives *dlp;
char base_dev[PATH_MAX];
int ppa;
if(strlen(ifname) > PATH_MAX) {
rp_fatal("socket: string to long");
}
ppa = atoi(&ifname[strlen(ifname)-1]);
strncpy(base_dev, ifname, PATH_MAX);
base_dev[strlen(base_dev)-1] = '\0';
/* rearranged order of DLPI code - delphys 20010803 */
dlp = (union DL_primitives*) buf;
if (( fd = open(base_dev, O_RDWR)) < 0) {
/* Give a more helpful message for the common error case */
if (errno == EPERM) {
rp_fatal("Cannot create raw socket -- pppoe must be run as root.");
}
fatalSys("socket");
}
/* rearranged order of DLPI code - delphys 20010803 */
dlattachreq(fd, ppa);
dlokack(fd, (char *)buf);
dlbindreq(fd, type, 0, DL_CLDLS, 0, 0);
dlbindack(fd, (char *)buf);
dlinforeq(fd);
dlinfoack(fd, (char *)buf);
dl_abssaplen = ABS(dlp->info_ack.dl_sap_length);
dl_saplen = dlp->info_ack.dl_sap_length;
if (ETHERADDRL != (dlp->info_ack.dl_addr_length - dl_abssaplen))
fatalSys("invalid destination physical address length");
dl_addrlen = dl_abssaplen + ETHERADDRL;
/* ethernet address retrieved as part of DL_INFO_ACK - delphys 20010803 */
memcpy(hwaddr, (u_char*)((char*)(dlp) + (int)(dlp->info_ack.dl_addr_offset)), ETHERADDRL);
if ( strioctl(fd, DLIOCRAW, -1, 0, NULL) < 0 ) {
fatalSys("DLIOCRAW");
}
if (ioctl(fd, I_FLUSH, FLUSHR) < 0) fatalSys("I_FLUSH");
return fd;
}
/* cloned from dlcommon.c */
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)
fatalSys("dlpromiscon: putmsg");
}
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)
fatalSys("dlinforeq: putmsg");
}
void dlunitdatareq(int fd, u_char *addrp, int addrlen, u_long minpri, u_long maxpri, u_char *datap, int datalen)
{
long buf[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;
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);
ctl.maxlen = 0;
ctl.len = sizeof (dl_unitdata_req_t) + addrlen;
ctl.buf = (char *) buf;
data.maxlen = 0;
data.len = datalen;
data.buf = (char *) datap;
if (putmsg(fd, &ctl, &data, 0) < 0)
fatalSys("dlunitdatareq: putmsg");
}
void dlinfoack(int fd, char *bufp)
{
union DL_primitives *dlp;
struct strbuf ctl;
int flags;
ctl.maxlen = MAXDLBUF;
ctl.len = 0;
ctl.buf = bufp;
strgetmsg(fd, &ctl, (struct strbuf*)NULL, &flags, "dlinfoack");
dlp = (union DL_primitives *) ctl.buf;
expecting(DL_INFO_ACK, dlp);
if (ctl.len < sizeof (dl_info_ack_t)) {
char buffer[256];
sprintf(buffer, "dlinfoack: response ctl.len too short: %d", ctl.len);
rp_fatal(buffer);
}
if (flags != RS_HIPRI)
rp_fatal("dlinfoack: DL_INFO_ACK was not M_PCPROTO");
if (ctl.len < sizeof (dl_info_ack_t)) {
char buffer[256];
sprintf(buffer, "dlinfoack: short response ctl.len: %d", ctl.len);
rp_fatal(buffer);
}
}
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;
bind_req.dl_xidtest_flg = xidtest;
ctl.maxlen = 0;
ctl.len = sizeof (bind_req);
ctl.buf = (char *) &bind_req;
flags = 0;
if (putmsg(fd, &ctl, (struct strbuf*) NULL, flags) < 0)
fatalSys("dlbindreq: putmsg");
}
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)
fatalSys("dlattachreq: putmsg");
}
void dlokack(int fd, char *bufp)
{
union DL_primitives *dlp;
struct strbuf ctl;
int flags;
ctl.maxlen = MAXDLBUF;
ctl.len = 0;
ctl.buf = bufp;
strgetmsg(fd, &ctl, (struct strbuf*)NULL, &flags, "dlokack");
dlp = (union DL_primitives *) ctl.buf;
expecting(DL_OK_ACK, dlp);
if (ctl.len < sizeof (dl_ok_ack_t)) {
char buffer[256];
sprintf(buffer, "dlokack: response ctl.len too short: %d", ctl.len);
rp_fatal(buffer);
}
if (flags != RS_HIPRI)
rp_fatal("dlokack: DL_OK_ACK was not M_PCPROTO");
if (ctl.len < sizeof (dl_ok_ack_t)) {
char buffer[256];
sprintf(buffer, "dlokack: short response ctl.len: %d", ctl.len);
rp_fatal(buffer);
}
}
void dlbindack(int fd, char *bufp)
{
union DL_primitives *dlp;
struct strbuf ctl;
int flags;
ctl.maxlen = MAXDLBUF;
ctl.len = 0;
ctl.buf = bufp;
strgetmsg(fd, &ctl, (struct strbuf*)NULL, &flags, "dlbindack");
dlp = (union DL_primitives *) ctl.buf;
expecting(DL_BIND_ACK, dlp);
if (flags != RS_HIPRI)
rp_fatal("dlbindack: DL_OK_ACK was not M_PCPROTO");
if (ctl.len < sizeof (dl_bind_ack_t)) {
char buffer[256];
sprintf(buffer, "dlbindack: short response ctl.len: %d", ctl.len);
rp_fatal(buffer);
}
}
int strioctl(int fd, int cmd, int timout, int len, char *dp)
{
struct strioctl sioc;
int rc;
sioc.ic_cmd = cmd;
sioc.ic_timout = timout;
sioc.ic_len = len;
sioc.ic_dp = dp;
rc = ioctl(fd, I_STR, &sioc);
if (rc < 0)
return (rc);
else
return (sioc.ic_len);
}
void strgetmsg(int fd, struct strbuf *ctlp, struct strbuf *datap, int *flagsp, char *caller)
{
int rc;
static char errmsg[80];
/*
* Start timer.
*/
(void) signal(SIGALRM, sigalrm);
if (alarm(MAXWAIT) < 0) {
(void) sprintf(errmsg, "%s: alarm", caller);
fatalSys(errmsg);
}
/*
* Set flags argument and issue getmsg().
*/
*flagsp = 0;
if ((rc = getmsg(fd, ctlp, datap, flagsp)) < 0) {
(void) sprintf(errmsg, "%s: getmsg", caller);
fatalSys(errmsg);
}
/*
* Stop timer.
*/
if (alarm(0) < 0) {
(void) sprintf(errmsg, "%s: alarm", caller);
fatalSys(errmsg);
}
/*
* Check for MOREDATA and/or MORECTL.
*/
if ((rc & (MORECTL | MOREDATA)) == (MORECTL | MOREDATA)) {
char buffer[256];
sprintf(buffer, "%s: MORECTL|MOREDATA", caller);
rp_fatal(buffer);
}
if (rc & MORECTL) {
char buffer[256];
sprintf(buffer, "%s: MORECTL", caller);
rp_fatal(buffer);
}
if (rc & MOREDATA) {
char buffer[256];
sprintf(buffer, "%s: MOREDATA", caller);
rp_fatal(buffer);
}
/*
* Check for at least sizeof (long) control data portion.
*/
if (ctlp->len < sizeof (long)) {
char buffer[256];
sprintf(buffer, "getmsg: control portion length < sizeof (long): %d", ctlp->len);
rp_fatal(buffer);
}
}
void sigalrm(int sig)
{
(void) rp_fatal("sigalrm: TIMEOUT");
}
void expecting(int prim, union DL_primitives *dlp)
{
if (dlp->dl_primitive != (u_long)prim) {
char buffer[256];
sprintf(buffer, "expected %s got %s", dlprim(prim), dlprim(dlp->dl_primitive));
rp_fatal(buffer);
exit(1);
}
}
char *dlprim(u_long prim)
{
static char primbuf[80];
switch ((int)prim) {
CASERET(DL_INFO_REQ);
CASERET(DL_INFO_ACK);
CASERET(DL_ATTACH_REQ);
CASERET(DL_DETACH_REQ);
CASERET(DL_BIND_REQ);
CASERET(DL_BIND_ACK);
CASERET(DL_UNBIND_REQ);
CASERET(DL_OK_ACK);
CASERET(DL_ERROR_ACK);
CASERET(DL_SUBS_BIND_REQ);
CASERET(DL_SUBS_BIND_ACK);
CASERET(DL_UNITDATA_REQ);
CASERET(DL_UNITDATA_IND);
CASERET(DL_UDERROR_IND);
CASERET(DL_UDQOS_REQ);
CASERET(DL_CONNECT_REQ);
CASERET(DL_CONNECT_IND);
CASERET(DL_CONNECT_RES);
CASERET(DL_CONNECT_CON);
CASERET(DL_TOKEN_REQ);
CASERET(DL_TOKEN_ACK);
CASERET(DL_DISCONNECT_REQ);
CASERET(DL_DISCONNECT_IND);
CASERET(DL_RESET_REQ);
CASERET(DL_RESET_IND);
CASERET(DL_RESET_RES);
CASERET(DL_RESET_CON);
default:
(void) sprintf(primbuf, "unknown primitive 0x%lx", prim);
return (primbuf);
}
}
#endif /* USE_DLPI */