bpf.c revision 252620
1193326Sed/*	$OpenBSD: bpf.c,v 1.13 2004/05/05 14:28:58 deraadt Exp $	*/
2193326Sed
3193326Sed/* BPF socket interface code, originally contributed by Archie Cobbs. */
4193326Sed
5193326Sed/*
6193326Sed * Copyright (c) 1995, 1996, 1998, 1999
7193326Sed * The Internet Software Consortium.    All rights reserved.
8193326Sed *
9193326Sed * Redistribution and use in source and binary forms, with or without
10193326Sed * modification, are permitted provided that the following conditions
11193326Sed * are met:
12193326Sed *
13193326Sed * 1. Redistributions of source code must retain the above copyright
14212904Sdim *    notice, this list of conditions and the following disclaimer.
15221345Sdim * 2. Redistributions in binary form must reproduce the above copyright
16193326Sed *    notice, this list of conditions and the following disclaimer in the
17193326Sed *    documentation and/or other materials provided with the distribution.
18263508Sdim * 3. Neither the name of The Internet Software Consortium nor the names
19198092Srdivacky *    of its contributors may be used to endorse or promote products derived
20249423Sdim *    from this software without specific prior written permission.
21239462Sdim *
22212904Sdim * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
23212904Sdim * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
24193326Sed * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
25221345Sdim * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26193326Sed * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
27193326Sed * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28198092Srdivacky * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29198092Srdivacky * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
30193326Sed * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
31249423Sdim * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32249423Sdim * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33249423Sdim * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34249423Sdim * SUCH DAMAGE.
35249423Sdim *
36249423Sdim * This software has been written for the Internet Software Consortium
37249423Sdim * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
38249423Sdim * Enterprises.  To learn more about the Internet Software Consortium,
39249423Sdim * see ``http://www.vix.com/isc''.  To learn more about Vixie
40249423Sdim * Enterprises, see ``http://www.vix.com''.
41249423Sdim */
42249423Sdim
43263508Sdim#include <sys/cdefs.h>
44234353Sdim__FBSDID("$FreeBSD: head/sbin/dhclient/bpf.c 252620 2013-07-03 22:01:52Z pjd $");
45201361Srdivacky
46193326Sed#include "dhcpd.h"
47198092Srdivacky#include <sys/ioctl.h>
48193326Sed#include <sys/uio.h>
49193326Sed
50212904Sdim#include <net/bpf.h>
51193326Sed#include <netinet/in_systm.h>
52224145Sdim#include <netinet/ip.h>
53224145Sdim#include <netinet/udp.h>
54224145Sdim#include <netinet/if_ether.h>
55224145Sdim
56224145Sdim#define BPF_FORMAT "/dev/bpf%d"
57224145Sdim
58212904Sdim/*
59193326Sed * Called by get_interface_list for each interface that's discovered.
60193326Sed * Opens a packet filter for each interface and adds it to the select
61234353Sdim * mask.
62234353Sdim */
63234353Sdimint
64234353Sdimif_register_bpf(struct interface_info *info, int flags)
65239462Sdim{
66239462Sdim	char filename[50];
67234353Sdim	int sock, b;
68234353Sdim
69234353Sdim	/* Open a BPF device */
70234353Sdim	for (b = 0;; b++) {
71234353Sdim		snprintf(filename, sizeof(filename), BPF_FORMAT, b);
72234353Sdim		sock = open(filename, flags);
73234353Sdim		if (sock < 0) {
74234353Sdim			if (errno == EBUSY)
75234353Sdim				continue;
76234353Sdim			else
77239462Sdim				error("Can't find free bpf: %m");
78234353Sdim		} else
79234353Sdim			break;
80234353Sdim	}
81234353Sdim
82239462Sdim	/* Set the BPF device to point at this interface. */
83234353Sdim	if (ioctl(sock, BIOCSETIF, info->ifp) < 0)
84234353Sdim		error("Can't attach interface %s to bpf device %s: %m",
85234353Sdim		    info->name, filename);
86234353Sdim
87239462Sdim	return (sock);
88239462Sdim}
89239462Sdim
90239462Sdim/*
91239462Sdim * Packet write filter program:
92239462Sdim * 'ip and udp and src port bootps and dst port (bootps or bootpc)'
93239462Sdim */
94239462Sdimstruct bpf_insn dhcp_bpf_wfilter[] = {
95239462Sdim	BPF_STMT(BPF_LD + BPF_B + BPF_IND, 14),
96239462Sdim	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (IPVERSION << 4) + 5, 0, 12),
97239462Sdim
98239462Sdim	/* Make sure this is an IP packet... */
99239462Sdim	BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
100239462Sdim	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 10),
101239462Sdim
102239462Sdim	/* Make sure it's a UDP packet... */
103239462Sdim	BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23),
104239462Sdim	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 8),
105239462Sdim
106239462Sdim	/* Make sure this isn't a fragment... */
107239462Sdim	BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20),
108239462Sdim	BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 6, 0),	/* patched */
109239462Sdim
110239462Sdim	/* Get the IP header length... */
111239462Sdim	BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14),
112239462Sdim
113263508Sdim	/* Make sure it's from the right port... */
114239462Sdim	BPF_STMT(BPF_LD + BPF_H + BPF_IND, 14),
115239462Sdim	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 68, 0, 3),
116239462Sdim
117239462Sdim	/* Make sure it is to the right ports ... */
118239462Sdim	BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16),
119239462Sdim	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1),
120239462Sdim
121239462Sdim	/* If we passed all the tests, ask for the whole packet. */
122239462Sdim	BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
123239462Sdim
124193326Sed	/* Otherwise, drop it. */
125193326Sed	BPF_STMT(BPF_RET+BPF_K, 0),
126193326Sed};
127193326Sed
128193326Sedint dhcp_bpf_wfilter_len = sizeof(dhcp_bpf_wfilter) / sizeof(struct bpf_insn);
129193326Sed
130193326Sedvoid
131193326Sedif_register_send(struct interface_info *info)
132251662Sdim{
133212904Sdim	struct bpf_version v;
134218893Sdim	struct bpf_program p;
135221345Sdim	int sock, on = 1;
136234353Sdim
137226633Sdim	/* Open a BPF device and hang it on this interface... */
138226633Sdim	info->wfdesc = if_register_bpf(info, O_WRONLY);
139199990Srdivacky
140199990Srdivacky	/* Make sure the BPF version is in range... */
141199990Srdivacky	if (ioctl(info->wfdesc, BIOCVERSION, &v) < 0)
142212904Sdim		error("Can't get BPF version: %m");
143199990Srdivacky
144199990Srdivacky	if (v.bv_major != BPF_MAJOR_VERSION ||
145207619Srdivacky	    v.bv_minor < BPF_MINOR_VERSION)
146199990Srdivacky		error("Kernel BPF version out of range - recompile dhcpd!");
147199990Srdivacky
148199990Srdivacky	/* Set up the bpf write filter program structure. */
149199990Srdivacky	p.bf_len = dhcp_bpf_wfilter_len;
150199990Srdivacky	p.bf_insns = dhcp_bpf_wfilter;
151199990Srdivacky
152199990Srdivacky	if (dhcp_bpf_wfilter[7].k == 0x1fff)
153199990Srdivacky		dhcp_bpf_wfilter[7].k = htons(IP_MF|IP_OFFMASK);
154199990Srdivacky
155199990Srdivacky	if (ioctl(info->wfdesc, BIOCSETWF, &p) < 0)
156199990Srdivacky		error("Can't install write filter program: %m");
157199990Srdivacky
158199990Srdivacky	if (ioctl(info->wfdesc, BIOCLOCK, NULL) < 0)
159234353Sdim		error("Cannot lock bpf");
160212904Sdim
161199990Srdivacky	/*
162210299Sed	 * Use raw socket for unicast send.
163210299Sed	 */
164221345Sdim	if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP)) == -1)
165221345Sdim		error("socket(SOCK_RAW): %m");
166221345Sdim	if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &on,
167221345Sdim	    sizeof(on)) == -1)
168212904Sdim		error("setsockopt(IP_HDRINCL): %m");
169221345Sdim	info->ufdesc = sock;
170221345Sdim}
171221345Sdim
172221345Sdim/*
173199990Srdivacky * Packet filter program...
174199990Srdivacky *
175212904Sdim * XXX: Changes to the filter program may require changes to the
176199990Srdivacky * constant offsets used in if_register_send to patch the BPF program!
177199990Srdivacky */
178207619Srdivackystruct bpf_insn dhcp_bpf_filter[] = {
179207619Srdivacky	/* Make sure this is an IP packet... */
180212904Sdim	BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
181198092Srdivacky	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),
182201361Srdivacky
183201361Srdivacky	/* Make sure it's a UDP packet... */
184201361Srdivacky	BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23),
185201361Srdivacky	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
186201361Srdivacky
187201361Srdivacky	/* Make sure this isn't a fragment... */
188199990Srdivacky	BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20),
189199990Srdivacky	BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
190199990Srdivacky
191199990Srdivacky	/* Get the IP header length... */
192199990Srdivacky	BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14),
193199990Srdivacky
194198092Srdivacky	/* Make sure it's to the right port... */
195199990Srdivacky	BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16),
196199990Srdivacky	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1),		/* patch */
197199990Srdivacky
198199990Srdivacky	/* If we passed all the tests, ask for the whole packet. */
199199990Srdivacky	BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
200199990Srdivacky
201199990Srdivacky	/* Otherwise, drop it. */
202199990Srdivacky	BPF_STMT(BPF_RET+BPF_K, 0),
203199990Srdivacky};
204199990Srdivacky
205199990Srdivackyint dhcp_bpf_filter_len = sizeof(dhcp_bpf_filter) / sizeof(struct bpf_insn);
206199990Srdivacky
207199990Srdivackyvoid
208199990Srdivackyif_register_receive(struct interface_info *info)
209193326Sed{
210199482Srdivacky	struct bpf_version v;
211193326Sed	struct bpf_program p;
212202379Srdivacky	int flag = 1, sz;
213226633Sdim
214239462Sdim	/* Open a BPF device and hang it on this interface... */
215226633Sdim	info->rfdesc = if_register_bpf(info, O_RDONLY);
216234353Sdim
217226633Sdim	/* Make sure the BPF version is in range... */
218226633Sdim	if (ioctl(info->rfdesc, BIOCVERSION, &v) < 0)
219226633Sdim		error("Can't get BPF version: %m");
220226633Sdim
221226633Sdim	if (v.bv_major != BPF_MAJOR_VERSION ||
222226633Sdim	    v.bv_minor < BPF_MINOR_VERSION)
223226633Sdim		error("Kernel BPF version out of range - recompile dhcpd!");
224226633Sdim
225226633Sdim	/*
226226633Sdim	 * Set immediate mode so that reads return as soon as a packet
227226633Sdim	 * comes in, rather than waiting for the input buffer to fill
228226633Sdim	 * with packets.
229226633Sdim	 */
230226633Sdim	if (ioctl(info->rfdesc, BIOCIMMEDIATE, &flag) < 0)
231226633Sdim		error("Can't set immediate mode on bpf device: %m");
232234353Sdim
233226633Sdim	/* Get the required BPF buffer length from the kernel. */
234226633Sdim	if (ioctl(info->rfdesc, BIOCGBLEN, &sz) < 0)
235226633Sdim		error("Can't get bpf buffer length: %m");
236226633Sdim	info->rbuf_max = sz;
237234353Sdim	info->rbuf = malloc(info->rbuf_max);
238226633Sdim	if (!info->rbuf)
239226633Sdim		error("Can't allocate %lu bytes for bpf input buffer.",
240263508Sdim		    (unsigned long)info->rbuf_max);
241263508Sdim	info->rbuf_offset = 0;
242263508Sdim	info->rbuf_len = 0;
243226633Sdim
244226633Sdim	/* Set up the bpf filter program structure. */
245226633Sdim	p.bf_len = dhcp_bpf_filter_len;
246226633Sdim	p.bf_insns = dhcp_bpf_filter;
247226633Sdim
248226633Sdim	/* Patch the server port into the BPF program...
249226633Sdim	 *
250226633Sdim	 * XXX: changes to filter program may require changes to the
251193326Sed	 * insn number(s) used below!
252199482Srdivacky	 */
253203955Srdivacky	dhcp_bpf_filter[8].k = LOCAL_PORT;
254212904Sdim
255193326Sed	if (ioctl(info->rfdesc, BIOCSETF, &p) < 0)
256198893Srdivacky		error("Can't install packet filter program: %m");
257198092Srdivacky
258198092Srdivacky	if (ioctl(info->rfdesc, BIOCLOCK, NULL) < 0)
259198092Srdivacky		error("Cannot lock bpf");
260198092Srdivacky}
261198092Srdivacky
262199482Srdivackyvoid
263199482Srdivackysend_packet(struct interface_info *interface, struct dhcp_packet *raw,
264212904Sdim    size_t len, struct in_addr from, struct in_addr to)
265199482Srdivacky{
266198092Srdivacky	unsigned char buf[256];
267193326Sed	struct iovec iov[2];
268193326Sed	struct msghdr msg;
269193326Sed	int result, bufp = 0;
270193326Sed
271198092Srdivacky	/* Assemble the headers... */
272198092Srdivacky	if (to.s_addr == INADDR_BROADCAST)
273193326Sed		assemble_hw_header(interface, buf, &bufp);
274193326Sed	assemble_udp_ip_header(buf, &bufp, from.s_addr, to.s_addr,
275193326Sed	    htons(REMOTE_PORT), (unsigned char *)raw, len);
276193326Sed
277193326Sed	iov[0].iov_base = buf;
278193326Sed	iov[0].iov_len = bufp;
279193326Sed	iov[1].iov_base = raw;
280193326Sed	iov[1].iov_len = len;
281193326Sed
282193326Sed	/* Fire it off */
283193326Sed	if (to.s_addr == INADDR_BROADCAST)
284193326Sed		result = writev(interface->wfdesc, iov, 2);
285199482Srdivacky	else {
286212904Sdim		struct sockaddr_in sato;
287193326Sed
288193326Sed		sato.sin_addr = to;
289193326Sed		sato.sin_port = htons(REMOTE_PORT);
290193326Sed		sato.sin_family = AF_INET;
291193326Sed		sato.sin_len = sizeof(sato);
292193326Sed
293193326Sed		memset(&msg, 0, sizeof(msg));
294193326Sed		msg.msg_name = (struct sockaddr *)&sato;
295193326Sed		msg.msg_namelen = sizeof(sato);
296198092Srdivacky		msg.msg_iov = iov;
297193326Sed		msg.msg_iovlen = 2;
298193326Sed		result = sendmsg(interface->ufdesc, &msg, 0);
299193326Sed	}
300198893Srdivacky
301198092Srdivacky	if (result < 0)
302198893Srdivacky		warning("send_packet: %m");
303198893Srdivacky}
304198893Srdivacky
305199482Srdivackyssize_t
306198893Srdivackyreceive_packet(struct interface_info *interface, unsigned char *buf,
307198893Srdivacky    size_t len, struct sockaddr_in *from, struct hardware *hfrom)
308234353Sdim{
309234353Sdim	int length = 0, offset = 0;
310234353Sdim	struct bpf_hdr hdr;
311234353Sdim
312234353Sdim	/*
313221345Sdim	 * All this complexity is because BPF doesn't guarantee that
314221345Sdim	 * only one packet will be returned at a time.  We're getting
315221345Sdim	 * what we deserve, though - this is a terrible abuse of the BPF
316221345Sdim	 * interface.  Sigh.
317221345Sdim	 */
318221345Sdim
319221345Sdim	/* Process packets until we get one we can return or until we've
320234353Sdim	 * done a read and gotten nothing we can return...
321221345Sdim	 */
322221345Sdim	do {
323221345Sdim		/* If the buffer is empty, fill it. */
324221345Sdim		if (interface->rbuf_offset >= interface->rbuf_len) {
325221345Sdim			length = read(interface->rfdesc, interface->rbuf,
326221345Sdim			    interface->rbuf_max);
327198893Srdivacky			if (length <= 0)
328221345Sdim				return (length);
329218893Sdim			interface->rbuf_offset = 0;
330218893Sdim			interface->rbuf_len = length;
331218893Sdim		}
332218893Sdim
333218893Sdim		/*
334199482Srdivacky		 * If there isn't room for a whole bpf header, something
335199482Srdivacky		 * went wrong, but we'll ignore it and hope it goes
336212904Sdim		 * away... XXX
337199482Srdivacky		 */
338212904Sdim		if (interface->rbuf_len - interface->rbuf_offset <
339193326Sed		    sizeof(hdr)) {
340193326Sed			interface->rbuf_offset = interface->rbuf_len;
341193326Sed			continue;
342193326Sed		}
343193326Sed
344243830Sdim		/* Copy out a bpf header... */
345243830Sdim		memcpy(&hdr, &interface->rbuf[interface->rbuf_offset],
346193326Sed		    sizeof(hdr));
347193326Sed
348199482Srdivacky		/*
349199482Srdivacky		 * If the bpf header plus data doesn't fit in what's
350199482Srdivacky		 * left of the buffer, stick head in sand yet again...
351199482Srdivacky		 */
352200583Srdivacky		if (interface->rbuf_offset + hdr.bh_hdrlen + hdr.bh_caplen >
353193326Sed		    interface->rbuf_len) {
354208600Srdivacky			interface->rbuf_offset = interface->rbuf_len;
355243830Sdim			continue;
356208600Srdivacky		}
357208600Srdivacky
358208600Srdivacky		/* Skip over the BPF header... */
359193326Sed		interface->rbuf_offset += hdr.bh_hdrlen;
360193326Sed
361198092Srdivacky		/*
362193326Sed		 * If the captured data wasn't the whole packet, or if
363193326Sed		 * the packet won't fit in the input buffer, all we can
364193326Sed		 * do is drop it.
365221345Sdim		 */
366221345Sdim		if (hdr.bh_caplen != hdr.bh_datalen) {
367221345Sdim			interface->rbuf_offset =
368221345Sdim			    BPF_WORDALIGN(interface->rbuf_offset +
369221345Sdim			    hdr.bh_caplen);
370221345Sdim			continue;
371221345Sdim		}
372221345Sdim
373221345Sdim		/* Decode the physical header... */
374221345Sdim		offset = decode_hw_header(interface->rbuf,
375221345Sdim		    interface->rbuf_offset, hfrom);
376221345Sdim
377221345Sdim		/*
378221345Sdim		 * If a physical layer checksum failed (dunno of any
379226633Sdim		 * physical layer that supports this, but WTH), skip
380221345Sdim		 * this packet.
381221345Sdim		 */
382221345Sdim		if (offset < 0) {
383221345Sdim			interface->rbuf_offset =
384221345Sdim			    BPF_WORDALIGN(interface->rbuf_offset +
385221345Sdim			    hdr.bh_caplen);
386221345Sdim			continue;
387221345Sdim		}
388226633Sdim		interface->rbuf_offset += offset;
389221345Sdim		hdr.bh_caplen -= offset;
390226633Sdim
391221345Sdim		/* Decode the IP and UDP headers... */
392221345Sdim		offset = decode_udp_ip_header(interface->rbuf,
393239462Sdim		    interface->rbuf_offset, from, NULL, hdr.bh_caplen);
394198092Srdivacky
395198092Srdivacky		/* If the IP or UDP checksum was bad, skip the packet... */
396207619Srdivacky		if (offset < 0) {
397212904Sdim			interface->rbuf_offset =
398198092Srdivacky			    BPF_WORDALIGN(interface->rbuf_offset +
399212904Sdim			    hdr.bh_caplen);
400198092Srdivacky			continue;
401201361Srdivacky		}
402201361Srdivacky		interface->rbuf_offset += offset;
403234353Sdim		hdr.bh_caplen -= offset;
404239462Sdim
405234353Sdim		/*
406234353Sdim		 * If there's not enough room to stash the packet data,
407224145Sdim		 * we have to skip it (this shouldn't happen in real
408224145Sdim		 * life, though).
409263508Sdim		 */
410263508Sdim		if (hdr.bh_caplen > len) {
411224145Sdim			interface->rbuf_offset =
412234353Sdim			    BPF_WORDALIGN(interface->rbuf_offset +
413263508Sdim			    hdr.bh_caplen);
414263508Sdim			continue;
415263508Sdim		}
416263508Sdim
417263508Sdim		/* Copy out the data in the packet... */
418263508Sdim		memcpy(buf, interface->rbuf + interface->rbuf_offset,
419263508Sdim		    hdr.bh_caplen);
420263508Sdim		interface->rbuf_offset =
421263508Sdim		    BPF_WORDALIGN(interface->rbuf_offset +
422263508Sdim		    hdr.bh_caplen);
423263508Sdim		return (hdr.bh_caplen);
424234353Sdim	} while (!length);
425263508Sdim	return (0);
426201361Srdivacky}
427263508Sdim