aboutsummaryrefslogtreecommitdiffstats
path: root/pcap.c
blob: 3c86a12bb6a6f64759f7279a706fcb592284aad2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/* pcap.c - does roughly the job of bpf.c from the NetBSD version sources 
 * 
 * This code was written in about 1996 by 
 * Peter Maydell <pmaydell@chiark.greenend.org.uk>, 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 <jongk@linux-m68k.org>)
 *
 * 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 <sys/types.h>
#include <sys/socket.h>				/* socket(), sendto() */
#include <netinet/in.h>				/* htons(), ntohs() */
#include <sys/ioctl.h>				/* ioctl() */
#include <unistd.h>				/* close() */
#include <syslog.h>				/* syslog() */
#include <net/if.h>				/* struct ifreq */
#include <net/ethernet.h>                       /* ETH_P_802_3 */
#include <netpacket/packet.h>                   /* PF_PACKET stuff */
#include <string.h>                          	/* 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 */
}