| Commit | Line | Data |
|---|---|---|
| 846204b6 | 1 | /* $OpenBSD: bpf.c,v 1.20 2007/01/08 02:51:13 krw Exp $ */ |
| c02c89a7 | 2 | /* $DragonFly: src/sbin/dhclient/bpf.c,v 1.2 2008/11/05 14:08:41 sephe Exp $ */ |
| 846204b6 HT |
3 | |
| 4 | /* BPF socket interface code, originally contributed by Archie Cobbs. */ | |
| 5 | ||
| 6 | /* | |
| 7 | * Copyright (c) 1995, 1996, 1998, 1999 | |
| 8 | * The Internet Software Consortium. All rights reserved. | |
| 9 | * | |
| 10 | * Redistribution and use in source and binary forms, with or without | |
| 11 | * modification, are permitted provided that the following conditions | |
| 12 | * are met: | |
| 13 | * | |
| 14 | * 1. Redistributions of source code must retain the above copyright | |
| 15 | * notice, this list of conditions and the following disclaimer. | |
| 16 | * 2. Redistributions in binary form must reproduce the above copyright | |
| 17 | * notice, this list of conditions and the following disclaimer in the | |
| 18 | * documentation and/or other materials provided with the distribution. | |
| 19 | * 3. Neither the name of The Internet Software Consortium nor the names | |
| 20 | * of its contributors may be used to endorse or promote products derived | |
| 21 | * from this software without specific prior written permission. | |
| 22 | * | |
| 23 | * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND | |
| 24 | * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, | |
| 25 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
| 26 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 27 | * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR | |
| 28 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 29 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 30 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |
| 31 | * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
| 32 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| 33 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | |
| 34 | * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
| 35 | * SUCH DAMAGE. | |
| 36 | * | |
| 37 | * This software has been written for the Internet Software Consortium | |
| 38 | * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie | |
| 39 | * Enterprises. To learn more about the Internet Software Consortium, | |
| 40 | * see ``http://www.vix.com/isc''. To learn more about Vixie | |
| 41 | * Enterprises, see ``http://www.vix.com''. | |
| 42 | */ | |
| 43 | ||
| 44 | #include <sys/ioctl.h> | |
| 45 | #include <sys/uio.h> | |
| 46 | ||
| 47 | #include <net/bpf.h> | |
| 48 | #include <netinet/if_ether.h> | |
| 49 | #include <netinet/in_systm.h> | |
| 50 | #include <netinet/ip.h> | |
| 51 | #include <netinet/udp.h> | |
| 52 | ||
| 53 | #include "dhcpd.h" | |
| 54 | ||
| 846204b6 HT |
55 | /* |
| 56 | * Called by get_interface_list for each interface that's discovered. | |
| 57 | * Opens a packet filter for each interface and adds it to the select | |
| 58 | * mask. | |
| 59 | */ | |
| 60 | int | |
| 61 | if_register_bpf(void) | |
| 62 | { | |
| 63 | char filename[50]; | |
| 64 | int sock, b; | |
| 65 | ||
| 45e80934 MD |
66 | /* |
| 67 | * Open a BPF device. Try auto-clone first (newer kernels), | |
| 68 | * otherwise iterate (older kernels). | |
| 69 | */ | |
| 70 | sock = open("/dev/bpf", O_RDWR, 0); | |
| 71 | if (sock < 0) { | |
| 72 | for (b = 0; 1; b++) { | |
| 73 | snprintf(filename, sizeof(filename), "/dev/bpf%d", b); | |
| 74 | sock = open(filename, O_RDWR, 0); | |
| 75 | if (sock < 0) { | |
| 76 | if (errno == EBUSY) | |
| 77 | continue; | |
| 78 | else | |
| 79 | error("Can't find free bpf: %m"); | |
| 80 | } else { | |
| 81 | break; | |
| 82 | } | |
| 83 | } | |
| 846204b6 HT |
84 | } |
| 85 | ||
| 86 | /* Set the BPF device to point at this interface. */ | |
| 87 | if (ioctl(sock, BIOCSETIF, ifi->ifp) < 0) | |
| 88 | error("Can't attach interface %s to bpf device %s: %m", | |
| 89 | ifi->name, filename); | |
| 90 | ||
| 91 | return (sock); | |
| 92 | } | |
| 93 | ||
| 94 | void | |
| 95 | if_register_send(void) | |
| 96 | { | |
| 97 | int sock, on = 1; | |
| 98 | ||
| 99 | /* | |
| 100 | * If we're using the bpf API for sending and receiving, we | |
| 101 | * don't need to register this interface twice. | |
| 102 | */ | |
| 103 | ifi->wfdesc = ifi->rfdesc; | |
| 104 | ||
| 105 | /* | |
| 106 | * Use raw socket for unicast send. | |
| 107 | */ | |
| 108 | if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP)) == -1) | |
| 109 | error("socket(SOCK_RAW): %m"); | |
| 110 | if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &on, | |
| 111 | sizeof(on)) == -1) | |
| 112 | error("setsockopt(IP_HDRINCL): %m"); | |
| 113 | ifi->ufdesc = sock; | |
| 114 | } | |
| 115 | ||
| 116 | /* | |
| 117 | * Packet filter program... | |
| 118 | * | |
| 119 | * XXX: Changes to the filter program may require changes to the | |
| 120 | * constant offsets used in if_register_send to patch the BPF program! | |
| 121 | */ | |
| 122 | struct bpf_insn dhcp_bpf_filter[] = { | |
| 123 | /* Make sure this is an IP packet... */ | |
| 124 | BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), | |
| 125 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8), | |
| 126 | ||
| 127 | /* Make sure it's a UDP packet... */ | |
| 128 | BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23), | |
| 129 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6), | |
| 130 | ||
| 131 | /* Make sure this isn't a fragment... */ | |
| 132 | BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20), | |
| 133 | BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0), | |
| 134 | ||
| 135 | /* Get the IP header length... */ | |
| 136 | BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14), | |
| 137 | ||
| 138 | /* Make sure it's to the right port... */ | |
| 139 | BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16), | |
| 140 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1), /* patch */ | |
| 141 | ||
| 142 | /* If we passed all the tests, ask for the whole packet. */ | |
| 143 | BPF_STMT(BPF_RET+BPF_K, (u_int)-1), | |
| 144 | ||
| 145 | /* Otherwise, drop it. */ | |
| 146 | BPF_STMT(BPF_RET+BPF_K, 0), | |
| 147 | }; | |
| 148 | ||
| 149 | int dhcp_bpf_filter_len = sizeof(dhcp_bpf_filter) / sizeof(struct bpf_insn); | |
| 150 | ||
| 151 | /* | |
| 152 | * Packet write filter program: | |
| 153 | * 'ip and udp and src port bootps and dst port (bootps or bootpc)' | |
| 154 | */ | |
| 155 | struct bpf_insn dhcp_bpf_wfilter[] = { | |
| 156 | BPF_STMT(BPF_LD + BPF_B + BPF_IND, 14), | |
| 157 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (IPVERSION << 4) + 5, 0, 12), | |
| 158 | ||
| 159 | /* Make sure this is an IP packet... */ | |
| 160 | BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), | |
| 161 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 10), | |
| 162 | ||
| 163 | /* Make sure it's a UDP packet... */ | |
| 164 | BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23), | |
| 165 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 8), | |
| 166 | ||
| 167 | /* Make sure this isn't a fragment... */ | |
| 168 | BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20), | |
| 169 | BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 6, 0), /* patched */ | |
| 170 | ||
| 171 | /* Get the IP header length... */ | |
| 172 | BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14), | |
| 173 | ||
| 174 | /* Make sure it's from the right port... */ | |
| 175 | BPF_STMT(BPF_LD + BPF_H + BPF_IND, 14), | |
| 176 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 68, 0, 3), | |
| 177 | ||
| 178 | /* Make sure it is to the right ports ... */ | |
| 179 | BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16), | |
| 180 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1), | |
| 181 | ||
| 182 | /* If we passed all the tests, ask for the whole packet. */ | |
| 183 | BPF_STMT(BPF_RET+BPF_K, (u_int)-1), | |
| 184 | ||
| 185 | /* Otherwise, drop it. */ | |
| 186 | BPF_STMT(BPF_RET+BPF_K, 0), | |
| 187 | }; | |
| 188 | ||
| 189 | int dhcp_bpf_wfilter_len = sizeof(dhcp_bpf_wfilter) / sizeof(struct bpf_insn); | |
| 190 | ||
| 191 | void | |
| 192 | if_register_receive(void) | |
| 193 | { | |
| 194 | struct bpf_version v; | |
| 195 | struct bpf_program p; | |
| 196 | int flag = 1, sz; | |
| 197 | ||
| 198 | /* Open a BPF device and hang it on this interface... */ | |
| 199 | ifi->rfdesc = if_register_bpf(); | |
| 200 | ||
| 201 | /* Make sure the BPF version is in range... */ | |
| 202 | if (ioctl(ifi->rfdesc, BIOCVERSION, &v) < 0) | |
| 203 | error("Can't get BPF version: %m"); | |
| 204 | ||
| 205 | if (v.bv_major != BPF_MAJOR_VERSION || | |
| 206 | v.bv_minor < BPF_MINOR_VERSION) | |
| 207 | error("Kernel BPF version out of range - recompile dhcpd!"); | |
| 208 | ||
| 209 | /* | |
| 210 | * Set immediate mode so that reads return as soon as a packet | |
| 211 | * comes in, rather than waiting for the input buffer to fill | |
| 212 | * with packets. | |
| 213 | */ | |
| 214 | if (ioctl(ifi->rfdesc, BIOCIMMEDIATE, &flag) < 0) | |
| 215 | error("Can't set immediate mode on bpf device: %m"); | |
| 216 | ||
| 217 | /*if (ioctl(ifi->rfdesc, BIOCSFILDROP, &flag) < 0) | |
| 218 | error("Can't set filter-drop mode on bpf device: %m");*/ | |
| 219 | ||
| 220 | /* Get the required BPF buffer length from the kernel. */ | |
| 221 | if (ioctl(ifi->rfdesc, BIOCGBLEN, &sz) < 0) | |
| 222 | error("Can't get bpf buffer length: %m"); | |
| 223 | ifi->rbuf_max = sz; | |
| 224 | ifi->rbuf = malloc(ifi->rbuf_max); | |
| 225 | if (!ifi->rbuf) | |
| 226 | error("Can't allocate %lu bytes for bpf input buffer.", | |
| 227 | (unsigned long)ifi->rbuf_max); | |
| 228 | ifi->rbuf_offset = 0; | |
| 229 | ifi->rbuf_len = 0; | |
| 230 | ||
| 231 | /* Set up the bpf filter program structure. */ | |
| 232 | p.bf_len = dhcp_bpf_filter_len; | |
| 233 | p.bf_insns = dhcp_bpf_filter; | |
| 234 | ||
| 235 | /* Patch the server port into the BPF program... | |
| 236 | * | |
| 237 | * XXX: changes to filter program may require changes to the | |
| 238 | * insn number(s) used below! | |
| 239 | */ | |
| 240 | dhcp_bpf_filter[8].k = LOCAL_PORT; | |
| 241 | ||
| 242 | if (ioctl(ifi->rfdesc, BIOCSETF, &p) < 0) | |
| 243 | error("Can't install packet filter program: %m"); | |
| 244 | ||
| 245 | /* Set up the bpf write filter program structure. */ | |
| 246 | p.bf_len = dhcp_bpf_wfilter_len; | |
| 247 | p.bf_insns = dhcp_bpf_wfilter; | |
| 248 | ||
| 249 | if (dhcp_bpf_wfilter[7].k == 0x1fff) | |
| 250 | dhcp_bpf_wfilter[7].k = htons(IP_MF|IP_OFFMASK); | |
| 251 | ||
| 252 | if (ioctl(ifi->rfdesc, BIOCSETWF, &p) < 0) | |
| 253 | error("Can't install write filter program: %m"); | |
| 254 | ||
| 255 | if (ioctl(ifi->rfdesc, BIOCLOCK, NULL) < 0) | |
| 256 | error("Cannot lock bpf"); | |
| 257 | } | |
| 258 | ||
| 259 | ssize_t | |
| 260 | send_packet(struct in_addr from, struct sockaddr_in *to, | |
| 261 | struct hardware *hto) | |
| 262 | { | |
| 263 | #define IOVCNT 2 | |
| 264 | unsigned char buf[256]; | |
| 265 | struct iovec iov[IOVCNT]; | |
| 266 | struct msghdr msg; | |
| 267 | int result, bufp = 0; | |
| 268 | ||
| 269 | if (to->sin_addr.s_addr == INADDR_BROADCAST) { | |
| 270 | assemble_hw_header(buf, &bufp, hto); | |
| 271 | } | |
| 272 | ||
| 273 | assemble_udp_ip_header(buf, &bufp, from.s_addr, | |
| 274 | to->sin_addr.s_addr, to->sin_port, | |
| 275 | (unsigned char *)&client->packet, | |
| 276 | client->packet_length); | |
| 277 | ||
| 278 | iov[0].iov_base = (char *)buf; | |
| 279 | iov[0].iov_len = bufp; | |
| 280 | iov[1].iov_base = (char *)&client->packet; | |
| 281 | iov[1].iov_len = client->packet_length; | |
| 282 | ||
| 283 | if (to->sin_addr.s_addr == INADDR_BROADCAST) { | |
| 284 | result = writev(ifi->wfdesc, iov, IOVCNT); | |
| 285 | } else { | |
| c02c89a7 SZ |
286 | struct ip *ip = (struct ip *)buf; |
| 287 | ||
| 288 | /* | |
| 289 | * DragonFly's raw socket expects ip_len/ip_off | |
| 290 | * in host byte order. | |
| 291 | */ | |
| 292 | ip->ip_len = ntohs(ip->ip_len); | |
| 293 | ip->ip_off = ntohs(ip->ip_off); | |
| 294 | ||
| 846204b6 HT |
295 | memset(&msg, 0, sizeof(msg)); |
| 296 | msg.msg_name = (struct sockaddr *)to; | |
| 297 | msg.msg_namelen = sizeof(*to); | |
| 298 | msg.msg_iov = iov; | |
| 299 | msg.msg_iovlen = IOVCNT; | |
| 300 | result = sendmsg(ifi->ufdesc, &msg, 0); | |
| 301 | } | |
| 302 | ||
| 303 | if (result == -1) | |
| 304 | warning("send_packet: %m"); | |
| 305 | return (result); | |
| 306 | } | |
| 307 | ||
| 308 | ssize_t | |
| 309 | receive_packet(struct sockaddr_in *from, struct hardware *hfrom) | |
| 310 | { | |
| 311 | int length = 0, offset = 0; | |
| 312 | struct bpf_hdr hdr; | |
| 313 | ||
| 314 | /* | |
| 315 | * All this complexity is because BPF doesn't guarantee that | |
| 316 | * only one packet will be returned at a time. We're getting | |
| 317 | * what we deserve, though - this is a terrible abuse of the BPF | |
| 318 | * interface. Sigh. | |
| 319 | */ | |
| 320 | ||
| 321 | /* Process packets until we get one we can return or until we've | |
| 322 | * done a read and gotten nothing we can return... | |
| 323 | */ | |
| 324 | do { | |
| 325 | /* If the buffer is empty, fill it. */ | |
| db2a0826 | 326 | if (ifi->rbuf_offset >= ifi->rbuf_len) { |
| 846204b6 HT |
327 | length = read(ifi->rfdesc, ifi->rbuf, ifi->rbuf_max); |
| 328 | if (length <= 0) | |
| 329 | return (length); | |
| 330 | ifi->rbuf_offset = 0; | |
| 331 | ifi->rbuf_len = BPF_WORDALIGN(length); | |
| 332 | } | |
| 333 | ||
| 334 | /* | |
| 335 | * If there isn't room for a whole bpf header, something | |
| 336 | * went wrong, but we'll ignore it and hope it goes | |
| 337 | * away... XXX | |
| 338 | */ | |
| 339 | if (ifi->rbuf_len - ifi->rbuf_offset < sizeof(hdr)) { | |
| 340 | ifi->rbuf_offset = ifi->rbuf_len; | |
| 341 | continue; | |
| 342 | } | |
| 343 | ||
| 344 | /* Copy out a bpf header... */ | |
| 345 | memcpy(&hdr, &ifi->rbuf[ifi->rbuf_offset], sizeof(hdr)); | |
| 346 | ||
| 347 | /* | |
| 348 | * If the bpf header plus data doesn't fit in what's | |
| 349 | * left of the buffer, stick head in sand yet again... | |
| 350 | */ | |
| 351 | if (ifi->rbuf_offset + hdr.bh_hdrlen + hdr.bh_caplen > | |
| 352 | ifi->rbuf_len) { | |
| 353 | ifi->rbuf_offset = ifi->rbuf_len; | |
| 354 | continue; | |
| 355 | } | |
| 356 | ||
| 357 | /* | |
| 358 | * If the captured data wasn't the whole packet, or if | |
| 359 | * the packet won't fit in the input buffer, all we can | |
| 360 | * do is drop it. | |
| 361 | */ | |
| 362 | if (hdr.bh_caplen != hdr.bh_datalen) { | |
| 363 | ifi->rbuf_offset = BPF_WORDALIGN( | |
| 364 | ifi->rbuf_offset + hdr.bh_hdrlen + | |
| 365 | hdr.bh_caplen); | |
| 366 | continue; | |
| 367 | } | |
| 368 | ||
| 369 | /* Skip over the BPF header... */ | |
| 370 | ifi->rbuf_offset += hdr.bh_hdrlen; | |
| 371 | ||
| 372 | /* Decode the physical header... */ | |
| 373 | offset = decode_hw_header(ifi->rbuf, ifi->rbuf_offset, hfrom); | |
| 374 | ||
| 375 | /* | |
| 376 | * If a physical layer checksum failed (dunno of any | |
| 377 | * physical layer that supports this, but WTH), skip | |
| 378 | * this packet. | |
| 379 | */ | |
| 380 | if (offset < 0) { | |
| 381 | ifi->rbuf_offset = BPF_WORDALIGN( | |
| 382 | ifi->rbuf_offset + hdr.bh_caplen); | |
| 383 | continue; | |
| 384 | } | |
| 385 | ifi->rbuf_offset += offset; | |
| 386 | hdr.bh_caplen -= offset; | |
| 387 | ||
| 388 | /* Decode the IP and UDP headers... */ | |
| 389 | offset = decode_udp_ip_header(ifi->rbuf, | |
| 390 | ifi->rbuf_offset, from, NULL, hdr.bh_caplen); | |
| 391 | ||
| 392 | /* If the IP or UDP checksum was bad, skip the packet... */ | |
| 393 | if (offset < 0) { | |
| 394 | ifi->rbuf_offset = BPF_WORDALIGN( | |
| 395 | ifi->rbuf_offset + hdr.bh_caplen); | |
| 396 | continue; | |
| 397 | } | |
| 398 | ifi->rbuf_offset += offset; | |
| 399 | hdr.bh_caplen -= offset; | |
| 400 | ||
| 401 | /* | |
| 402 | * If there's not enough room to stash the packet data, | |
| 403 | * we have to skip it (this shouldn't happen in real | |
| 404 | * life, though). | |
| 405 | */ | |
| 406 | if (hdr.bh_caplen > sizeof(client->packet)) { | |
| 407 | ifi->rbuf_offset = BPF_WORDALIGN( | |
| 408 | ifi->rbuf_offset + hdr.bh_caplen); | |
| 409 | continue; | |
| 410 | } | |
| 411 | ||
| 412 | /* Copy out the data in the packet... */ | |
| 413 | memset(&client->packet, DHO_END, sizeof(client->packet)); | |
| 414 | memcpy(&client->packet, ifi->rbuf + ifi->rbuf_offset, | |
| 415 | hdr.bh_caplen); | |
| 416 | ifi->rbuf_offset = BPF_WORDALIGN(ifi->rbuf_offset + | |
| 417 | hdr.bh_caplen); | |
| 418 | return (hdr.bh_caplen); | |
| 419 | } while (!length); | |
| 420 | return (0); | |
| 421 | } |