1/* 2 * Copyright (c) 2000-2013 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24/* 25 * bootp_session.c 26 * - maintain BOOTP client socket session 27 * - maintain list of BOOTP clients 28 * - distribute packet reception to enabled clients 29 */ 30/* 31 * Modification History 32 * 33 * May 10, 2000 Dieter Siegmund (dieter@apple.com) 34 * - created 35 */ 36 37 38#include <stdlib.h> 39#include <unistd.h> 40#include <string.h> 41#include <stdio.h> 42#include <sys/types.h> 43#include <sys/wait.h> 44#include <sys/errno.h> 45#include <sys/socket.h> 46#include <sys/ioctl.h> 47#include <sys/sockio.h> 48#include <sys/filio.h> 49#include <ctype.h> 50#include <net/if.h> 51#include <net/ethernet.h> 52#include <netinet/in.h> 53#include <netinet/udp.h> 54#include <netinet/in_systm.h> 55#include <netinet/ip.h> 56#include <netinet/bootp.h> 57#include <arpa/inet.h> 58#include <net/if_types.h> 59#include <net/if_dl.h> 60#include "dhcp_options.h" 61#include "util.h" 62#include <syslog.h> 63#include <sys/uio.h> 64 65 66#include "dhcplib.h" 67#include "dynarray.h" 68#include "bootp_session.h" 69#include "bootp_transmit.h" 70#include "ipconfigd_globals.h" 71#include "timer.h" 72 73static bool S_verbose; 74 75struct bootp_session { 76 dynarray_t clients; 77 FDCalloutRef read_fd; 78 int read_fd_refcount; 79 uint16_t client_port; 80 timer_callout_t * timer_callout; 81 bool verbose; 82}; 83 84struct bootp_client { 85 bootp_session_t * session; /* pointer to parent */ 86 interface_t * if_p; 87 boolean_t fd_open; 88 bootp_receive_func_t * receive; 89 void * receive_arg1; 90 void * receive_arg2; 91}; 92 93 94static void 95bootp_session_read(void * arg1, void * arg2); 96 97static int 98S_open_bootp_socket(uint16_t client_port) 99{ 100 struct sockaddr_in me; 101 int status; 102 int opt; 103 int sockfd; 104 105 sockfd = socket(AF_INET, SOCK_DGRAM, 0); 106 if (sockfd < 0) { 107 perror("socket"); 108 return (-1); 109 } 110 bzero((char *)&me, sizeof(me)); 111 me.sin_family = AF_INET; 112 me.sin_port = htons(client_port); 113 me.sin_addr.s_addr = htonl(INADDR_ANY); 114 status = bind(sockfd, (struct sockaddr *)&me, sizeof(me)); 115 if (status != 0) { 116 my_log(LOG_ERR, "bootp_session: bind port %d failed, %s", 117 client_port, strerror(errno)); 118 goto failed; 119 } 120 opt = 1; 121 status = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt)); 122 if (status < 0) { 123 my_log(LOG_ERR, "setsockopt SO_BROADCAST failed, %s", 124 strerror(errno)); 125 goto failed; 126 } 127 status = ioctl(sockfd, FIONBIO, &opt); 128 if (status < 0) { 129 my_log(LOG_ERR, "ioctl FIONBIO failed, %s", strerror(errno)); 130 goto failed; 131 } 132 status = setsockopt(sockfd, IPPROTO_IP, IP_RECVIF, &opt, sizeof(opt)); 133 if (status < 0) { 134 my_log(LOG_ERR, "setsockopt IP_RECVIF failed, %s", 135 strerror(errno)); 136 goto failed; 137 } 138#ifdef SO_TC_CTL 139 opt = SO_TC_CTL; 140 /* set traffic class, we don't care if it failed. */ 141 (void)setsockopt(sockfd, SOL_SOCKET, SO_TRAFFIC_CLASS, &opt, 142 sizeof(opt)); 143#endif /* SO_TC_CTL */ 144 return sockfd; 145 146 failed: 147 if (sockfd >= 0) { 148 close(sockfd); 149 } 150 return (-1); 151} 152 153bootp_client_t * 154bootp_client_init(bootp_session_t * session, interface_t * if_p) 155{ 156 bootp_client_t * client; 157 158 client = malloc(sizeof(*client)); 159 if (client == NULL) 160 return (NULL); 161 bzero(client, sizeof(*client)); 162 if (dynarray_add(&session->clients, client) == FALSE) { 163 free(client); 164 return (NULL); 165 } 166 client->session = session; 167 client->if_p = if_p; 168 return (client); 169} 170 171static void 172bootp_client_free_element(void * arg) 173{ 174 bootp_client_t * client = (bootp_client_t *)arg; 175 176 bootp_client_disable_receive(client); 177 free(client); 178 return; 179} 180 181void 182bootp_client_free(bootp_client_t * * client_p) 183{ 184 bootp_client_t * client = *client_p; 185 int i; 186 bootp_session_t * session; 187 188 if (client == NULL) { 189 return; 190 } 191 session = client->session; 192 i = dynarray_index(&session->clients, client); 193 if (i != -1) { 194 dynarray_remove(&session->clients, i, NULL); 195 } 196 else { 197 my_log(LOG_ERR, "bootp_client_free(%s) not in list?", 198 if_name(client->if_p)); 199 } 200 bootp_client_free_element(client); 201 *client_p = NULL; 202 return; 203} 204 205static void 206bootp_session_delayed_close(void * arg1, void * arg2, void * arg3) 207{ 208 bootp_session_t * session = (bootp_session_t *)arg1; 209 210 if (session->read_fd == NULL) { 211 my_log(LOG_ERR, 212 "bootp_session_delayed_close(): socket is already closed"); 213 return; 214 } 215 if (session->read_fd_refcount > 0) { 216 my_log(LOG_ERR, 217 "bootp_session_delayed_close(): called when socket in use"); 218 return; 219 } 220 my_log(LOG_DEBUG, 221 "bootp_session_delayed_close(): closing bootp socket %d", 222 FDCalloutGetFD(session->read_fd)); 223 224 /* this closes the file descriptor */ 225 FDCalloutRelease(&session->read_fd); 226 return; 227} 228 229static void 230bootp_client_close_socket(bootp_client_t * client) 231{ 232 bootp_session_t * session = client->session; 233 234 if (client->fd_open == FALSE) { 235 return; 236 } 237 if (session->read_fd_refcount <= 0) { 238 my_log(LOG_NOTICE, "bootp_client_close_socket(%s): refcount %d", 239 if_name(client->if_p), session->read_fd_refcount); 240 return; 241 } 242 session->read_fd_refcount--; 243 my_log(LOG_DEBUG, "bootp_client_close_socket(%s): refcount %d", 244 if_name(client->if_p), session->read_fd_refcount); 245 client->fd_open = FALSE; 246 if (session->read_fd_refcount == 0) { 247 struct timeval tv; 248 249 my_log(LOG_DEBUG, 250 "bootp_client_close_socket(): scheduling delayed close"); 251 252 tv.tv_sec = 1; /* close it after 1 second of non-use */ 253 tv.tv_usec = 0; 254 timer_set_relative(session->timer_callout, tv, 255 bootp_session_delayed_close, 256 session, NULL, NULL); 257 } 258 return; 259} 260 261static boolean_t 262bootp_client_open_socket(bootp_client_t * client) 263{ 264 bootp_session_t * session = client->session; 265 266 if (client->fd_open) { 267 return (TRUE); 268 } 269 timer_cancel(session->timer_callout); 270 session->read_fd_refcount++; 271 my_log(LOG_DEBUG, "bootp_client_open_socket (%s): refcount %d", 272 if_name(client->if_p), session->read_fd_refcount); 273 client->fd_open = TRUE; 274 if (session->read_fd_refcount > 1) { 275 /* already open */ 276 return (TRUE); 277 } 278 if (session->read_fd != NULL) { 279 my_log(LOG_DEBUG, "bootp_client_open_socket(): socket is still open"); 280 } 281 else { 282 int sockfd; 283 284 sockfd = S_open_bootp_socket(session->client_port); 285 if (sockfd < 0) { 286 my_log(LOG_ERR, 287 "bootp_client_open_socket: S_get_bootp_socket() failed, %s", 288 strerror(errno)); 289 goto failed; 290 } 291 my_log(LOG_DEBUG, 292 "bootp_client_open_socket(): opened bootp socket %d", 293 sockfd); 294 /* register as a reader */ 295 session->read_fd = FDCalloutCreate(sockfd, 296 bootp_session_read, 297 session, NULL); 298 } 299 return (TRUE); 300 301 failed: 302 bootp_client_close_socket(client); 303 return (FALSE); 304} 305 306void 307bootp_client_enable_receive(bootp_client_t * client, 308 bootp_receive_func_t * func, 309 void * arg1, void * arg2) 310{ 311 client->receive = func; 312 client->receive_arg1 = arg1; 313 client->receive_arg2 = arg2; 314 if (bootp_client_open_socket(client) == FALSE) { 315 my_log(LOG_ERR, "bootp_client_enable_receive(%s): failed", 316 if_name(client->if_p)); 317 } 318 return; 319} 320 321void 322bootp_client_disable_receive(bootp_client_t * client) 323{ 324 client->receive = NULL; 325 client->receive_arg1 = NULL; 326 client->receive_arg2 = NULL; 327 bootp_client_close_socket(client); 328 return; 329} 330 331static void 332bootp_client_bind_socket_to_if(bootp_client_t * client, int opt) 333{ 334 bootp_session_t * session = client->session; 335 int fd = -1; 336 337 if (session->read_fd != NULL) { 338 fd = FDCalloutGetFD(session->read_fd); 339 } 340 if (fd < 0) { 341 my_log(LOG_ERR, 342 "bootp_client_bind_socket_to_if(%s, %d):" 343 " session socket isn't open", if_name(client->if_p), opt); 344 } 345 else if (setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &opt, sizeof(opt)) < 0) { 346 my_log(LOG_ERR, 347 "bootp_client_bind_socket_to_if(%s, %d):" 348 " setsockopt IP_BOUND_IF failed", 349 if_name(client->if_p), opt, strerror(errno)); 350 } 351 return; 352} 353 354int 355bootp_client_transmit(bootp_client_t * client, 356 struct in_addr dest_ip, 357 struct in_addr src_ip, 358 uint16_t dest_port, 359 uint16_t src_port, 360 void * data, int len) 361{ 362 int error; 363 int if_index = 0; 364 boolean_t needs_close = FALSE; 365 /* 366 * send_buf is cast to some struct types containing short fields; 367 * force it to be aligned as much as an int 368 */ 369 int send_buf_aligned[512]; 370 char * send_buf = (char *)send_buf_aligned; 371 bootp_session_t * session = client->session; 372 int sockfd = -1; 373 374 /* if we're not broadcasting, bind the socket to the interface */ 375 if (dest_ip.s_addr != INADDR_BROADCAST) { 376 if (client->fd_open == FALSE) { 377 /* open the BOOTP socket in case it's needed */ 378 (void)bootp_client_open_socket(client); 379 needs_close = TRUE; 380 } 381 if_index = if_link_index(client->if_p); 382 if (if_index != 0) { 383 bootp_client_bind_socket_to_if(client, if_index); 384 } 385 } 386 if (S_verbose) { 387 CFMutableStringRef str; 388 389 str = CFStringCreateMutable(NULL, 0); 390 dhcp_packet_print_cfstr(str, (struct dhcp *)data, len); 391 if (if_index != 0) { 392 my_log(-LOG_DEBUG, 393 "[%s] Transmit %d byte packet dest " 394 IP_FORMAT 395 " scope %d\n%@", 396 if_name(client->if_p), len, 397 IP_LIST(&dest_ip), if_index, str); 398 } 399 else { 400 my_log(-LOG_DEBUG, 401 "[%s] Transmit %d byte packet\n%@", 402 if_name(client->if_p), len, str); 403 } 404 CFRelease(str); 405 } 406 if (session->read_fd != NULL) { 407 sockfd = FDCalloutGetFD(session->read_fd); 408 } 409 error = bootp_transmit(sockfd, send_buf, 410 if_name(client->if_p), 411 if_link_arptype(client->if_p), NULL, 0, 412 dest_ip, src_ip, dest_port, src_port, data, len); 413 if (needs_close) { 414 bootp_client_close_socket(client); 415 } 416 if (if_index != 0) { 417 bootp_client_bind_socket_to_if(client, 0); 418 } 419 return (error); 420} 421 422 423 424bootp_session_t * 425bootp_session_init(uint16_t client_port) 426{ 427 bootp_session_t * session = malloc(sizeof(*session)); 428 if (session == NULL) 429 return (NULL); 430 bzero(session, sizeof(*session)); 431 dynarray_init(&session->clients, bootp_client_free_element, NULL); 432 session->client_port = client_port; 433 session->timer_callout = timer_callout_init(); 434 435 return (session); 436} 437 438void 439bootp_session_free(bootp_session_t * * session_p) 440{ 441 bootp_session_t * session = *session_p; 442 443 dynarray_free(&session->clients); 444 FDCalloutRelease(&session->read_fd); 445 timer_callout_free(&session->timer_callout); 446 bzero(session, sizeof(*session)); 447 free(session); 448 *session_p = NULL; 449 return; 450} 451 452static void 453bootp_session_deliver(bootp_session_t * session, const char * ifname, 454 void * data, int size) 455{ 456 bootp_receive_data_t event; 457 int i; 458 459 if (size < sizeof(struct dhcp)) { 460 return; 461 } 462 463 if (S_verbose) { 464 CFMutableStringRef str; 465 466 str = CFStringCreateMutable(NULL, 0); 467 dhcp_packet_print_cfstr(str, (struct dhcp *)data, size); 468 my_log(-LOG_DEBUG, 469 "[%s] Receive %d byte packet\n%@", 470 ifname, size, str); 471 CFRelease(str); 472 } 473 474 bzero(&event, sizeof(event)); 475 event.data = (struct dhcp *)data; 476 event.size = size; 477 dhcpol_parse_packet(&event.options, (struct dhcp *)data, size, NULL); 478 for (i = 0; i < dynarray_count(&session->clients); i++) { 479 bootp_client_t * client; 480 481 client = dynarray_element(&session->clients, i); 482 if (strcmp(if_name(client->if_p), ifname) != 0) { 483 continue; 484 } 485 if (client->receive) { 486 (*client->receive)(client->receive_arg1, client->receive_arg2, 487 &event); 488 } 489 } 490 dhcpol_free(&event.options); /* free malloc'd data */ 491 return; 492} 493 494static void * 495msghdr_parse_control(struct msghdr * msg_p, int level, int type, int * len) 496{ 497 struct cmsghdr * cmsg; 498 499 *len = 0; 500 for (cmsg = CMSG_FIRSTHDR(msg_p); cmsg; cmsg = CMSG_NXTHDR(msg_p, cmsg)) { 501 if (cmsg->cmsg_level == level 502 && cmsg->cmsg_type == type) { 503 if (cmsg->cmsg_len < sizeof(*cmsg)) 504 return (NULL); 505 *len = cmsg->cmsg_len - sizeof(*cmsg); 506 return (CMSG_DATA(cmsg)); 507 } 508 } 509 return (NULL); 510} 511 512static boolean_t 513msghdr_copy_ifname(struct msghdr * msg_p, char * ifname, int ifname_size) 514{ 515 int len = 0; 516 struct sockaddr_dl * dl_p; 517 518 dl_p = (struct sockaddr_dl *)msghdr_parse_control(msg_p, 519 IPPROTO_IP, IP_RECVIF, 520 &len); 521 if (dl_p == NULL || len == 0 || dl_p->sdl_nlen >= ifname_size) { 522 return (FALSE); 523 } 524 bcopy(dl_p->sdl_data, ifname, dl_p->sdl_nlen); 525 ifname[dl_p->sdl_nlen] = '\0'; 526 return (TRUE); 527} 528 529static void 530bootp_session_read(void * arg1, void * arg2) 531{ 532 char control[512]; 533 struct sockaddr_in from; 534 char ifname[IFNAMSIZ + 1]; 535 struct iovec iov; 536 struct msghdr msg; 537 int n; 538 uint32_t receive_buf[2048/(sizeof(uint32_t))]; 539 bootp_session_t * session = (bootp_session_t *)arg1; 540 541 msg.msg_name = (caddr_t)&from; 542 msg.msg_namelen = sizeof(from); 543 msg.msg_iov = &iov; 544 msg.msg_iovlen = 1; 545 msg.msg_control = control; 546 msg.msg_controllen = sizeof(control); 547 msg.msg_flags = 0; 548 iov.iov_base = (caddr_t)receive_buf; 549 iov.iov_len = sizeof(receive_buf); 550 n = recvmsg(FDCalloutGetFD(session->read_fd), &msg, 0); 551 if (n > 0) { 552 if (msghdr_copy_ifname(&msg, ifname, sizeof(ifname))) { 553 /* ALIGN: receive_buf aligned to uint64_t bytes */ 554 bootp_session_deliver(session, ifname, 555 (void *)receive_buf, n); 556 } 557 } 558 else if (n < 0) { 559 if (errno != EAGAIN) { 560 my_log(LOG_ERR, "bootp_session_read(): recvmsg failed, %s", 561 strerror(errno)); 562 } 563 } 564 return; 565} 566 567void 568bootp_session_set_verbose(bool verbose) 569{ 570 S_verbose = verbose; 571 return; 572} 573