/* pcap.c - does roughly the job of bpf.c from the NetBSD version sources * * This code was written in about 1996 by * Peter Maydell , and revised * in 1998 to clean it up a little... * * Revised to use PF_PACKET instead of SOCK_PACKET (thanks to patch from * Kars de Jong ) * * On the positive side, this code is rather less nasty than bpf.c, * primarily because a lot of lower-level stuff has vanished into libpcap. * * Provides : * PcapGetIntfName(char* errmsg) get first ethernet interface name, * or put an error message into errmsg buffer * * PcapOpen() open the packet filter, etc * PcapDispatch(handlerfcn) dispatch a packet if one present * PcapHandler() internal function - passes packets to * user's handler. * PcapClose() close filter, deinitialise, etc * * The legal bumph from the NetBSD sources is huge. Consider it said. */ #include #include /* socket(), sendto() */ #include /* htons(), ntohs() */ #include /* ioctl() */ #include /* close() */ #include /* syslog() */ #include /* struct ifreq */ #include /* ETH_P_802_3 */ #include /* PF_PACKET stuff */ #include /* strncpy(), bcopy() */ #include "defs.h" static pcap_t *pdesc = NULL; /* packet capture descriptor */ static char errbuf[PCAP_ERRBUF_SIZE]; /* buffer we use to get error msgs */ /* PcapOpen() - open & init packet filter. No params/return code. * If we encounter an error, we stop here (since that's what BpfOpen does) */ void PcapOpen() { int n; /* First we open the device. We use promiscuous mode as I have no * idea whether or not pcap supports multicast. Besides, there's not * going to be much net traffic for *my* application :-> * We set capture length to twice the size of the RMP packet * as a cheap way to detect oversize packets. This is a nasty hack... */ pdesc = pcap_open_live(IntfName, 3 * sizeof(struct rmp_packet), 1, 3, errbuf); if (pdesc == NULL) { syslog(LOG_ERR, "pcap: couldn't open filter: %s",errbuf); Exit(0); } /* check we're being used on an Ethernet device */ n = pcap_datalink(pdesc); if (n != DLT_EN10MB) { syslog(LOG_ERR, "pcap: %s: datalink type %d unsupported",IntfName, n); Exit(0); } /* Now set the filter program; this is straight from bpf.c */ { #define RMP ((struct rmp_packet *)0) static struct bpf_insn bpf_insn[] = { { BPF_LD|BPF_B|BPF_ABS, 0, 0, (long)&RMP->hp_llc.dsap }, { BPF_JMP|BPF_JEQ|BPF_K, 0, 5, IEEE_DSAP_HP }, { BPF_LD|BPF_H|BPF_ABS, 0, 0, (long)&RMP->hp_llc.cntrl }, { BPF_JMP|BPF_JEQ|BPF_K, 0, 3, IEEE_CNTL_HP }, { BPF_LD|BPF_H|BPF_ABS, 0, 0, (long)&RMP->hp_llc.dxsap }, { BPF_JMP|BPF_JEQ|BPF_K, 0, 1, HPEXT_DXSAP }, { BPF_RET|BPF_K, 0, 0, RMP_MAX_PACKET }, { BPF_RET|BPF_K, 0, 0, 0x0 } }; #undef RMP static struct bpf_program bpf_pgm = { sizeof(bpf_insn)/sizeof(bpf_insn[0]), bpf_insn }; if (pcap_setfilter(pdesc, &bpf_pgm) != 0) { syslog(LOG_ERR, "pcap: failed to set filter (bug?)"); Exit(0); } } } /* PcapGetIntfName - get name of the first ethernet device in the system. * We ignore loopback. We return 0 if none found, and then *errmsg * explains why. Why do we pass the errmsg buffer as char**? We only * dereference it here, and we use address-of to pass it in rbootd.c... * Ah well, presumably there was a reason :-> */ char* PcapGetIntfName(char **errmsg) { static char netdev[31]; pcap_if_t *alldevsp; int ret; /* We'll assume this function is suitable... */ ret = pcap_findalldevs(&alldevsp, *errmsg); /* find a device */ if (ret != 0) { return NULL; /* flag error to caller */ } strcpy(netdev, alldevsp->name); pcap_freealldevs(alldevsp); return netdev; /* device found - return ptr to its name */ } /* var used to pass the user's handler from PcapDispatch to PcapHandler */ static void (*uhandler)(RMPCONN rconn); void PcapHandler(u_char *user, const struct pcap_pkthdr *h, const u_char *sp) { /* Called when we get a packet. Sort it out into the right format for * the user's handler. Then call that. * This is probably the only really tricky bit in this file. * user is just the ptr passed to pcap_dispatch() - we don't use it. * h is ptr to the header. sp is the pointer to the actual packet. * You can get h->caplen bytes of data from sp onwards. * Header length is just sizeof(struct pcap_pkthdr). */ RMPCONN rconn; /* fill this in with packet details */ register int datlen, caplen; /* declared register because bpf.c does... */ datlen = h->len; /* length of this packet (untruncated) */ caplen = h->caplen; /* max. captured bytes */ /* First we ensure we got a complete packet of the right size! */ if (caplen < datlen) syslog(LOG_ERR, "bpf: truncated packet dropped (%d of %d bytes)", caplen, datlen); else if (datlen > sizeof(struct rmp_packet)) syslog(LOG_ERR, "bpf: oversized packet dropped (%d bytes)", caplen); else { /* fill in fields in rconn and pass packet to user handler */ rconn.rmplen = datlen; bcopy((char *)&h->ts, (char *)&rconn.tstamp, sizeof(struct timeval)); bcopy(sp, (char *)&rconn.rmp, datlen); /* and copy packet over */ uhandler(rconn); } } /* PcapDispatch(handlerfcn) : just an interface to the real * dispatch / handler code... * Takes one parameter - the user's handler, which has prototype: * void DealWithPacket(RMPCONN rconn); */ int PcapDispatch(void (*usrhandler)(RMPCONN rconn)) { int retcode; /* First we save ptr to the user's handler code */ uhandler = usrhandler; /* Then we use pcap_dispatch() to start things off, using a ptr to the real PcapHandler() */ retcode = pcap_dispatch(pdesc, 1, PcapHandler, NULL); /* We return the errorcode ( < 0 for errors) * but we syslog it here, since only we know the pdesc */ if (retcode < 0) syslog(LOG_ERR, "pcap: dispatch failed: %s", pcap_geterr(pdesc)); return retcode; } /* PcapClose() - close filter, deinitialise anything we did, etc. */ void PcapClose() { /* call pcap_close() to shut down interface... ...but only if we've already opened it! */ if (pdesc != NULL) pcap_close(pdesc); /* now do our own deinit, if any */ } /* We also need to be able to write packets to the Ethernet... This facility isn't provided by libpcap, but the subroutine's in this source file anyway. */ int PcapWrite(RMPCONN* rconn) { /* write rconn direct to ethernet */ /* What we get in an RMPCONN: * rconn->rmp is a struct rmp_packet * rconn->rmplen is the length of said packet * rconn->tstamp * rconn->bootfd filedesc of open bootfile * rconn->next ptr to next RMPCONN... * * We use a PF_PACKET socket in IEEE 802.3 mode. * All we need to do is fill out the destination addres and send rconn->rmp, * skipping the Ethernet header (it will be filled in by the kernel). */ int s,cs; struct sockaddr_ll sa; memset (&sa, 0, sizeof(sa)); s=socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_802_3)); if (s == -1) { syslog(LOG_ERR, "attempt to open socket for write failed!"); return 1; } /* Get the interface index to be used. */ { struct ifreq req; int status; strcpy (req.ifr_name, IntfName); status = ioctl (s, SIOCGIFINDEX, &req); if (status == -1) { syslog(LOG_ERR, "SIOCGIFINDEX: couldn't get interface index!"); return 0; } sa.sll_ifindex = req.ifr_ifindex; } /* Fill in the socket address */ sa.sll_protocol = htons(ETH_P_802_3); sa.sll_family = AF_PACKET; memcpy (&sa.sll_addr, &(rconn->rmp.hp_hdr.daddr), RMP_ADDRLEN); sa.sll_halen = RMP_ADDRLEN; /* perform the actual packet send */ cs = sendto(s, &(rconn->rmp.hp_llc), rconn->rmplen - sizeof(struct hp_hdr), 0, (struct sockaddr *)&sa, sizeof(sa)); close (s); if (cs == -1) { syslog (LOG_ERR, "sendto: failed to send packet!"); return 0; } return 1; /* success */ }