Annotation of embedaddon/strongswan/src/libcharon/plugins/dhcp/dhcp_socket.c, revision 1.1
1.1 ! misho 1: /*
! 2: * Copyright (C) 2012-2018 Tobias Brunner
! 3: * HSR Hochschule fuer Technik Rapperswil
! 4: *
! 5: * Copyright (C) 2010 Martin Willi
! 6: * Copyright (C) 2010 revosec AG
! 7: *
! 8: * This program is free software; you can redistribute it and/or modify it
! 9: * under the terms of the GNU General Public License as published by the
! 10: * Free Software Foundation; either version 2 of the License, or (at your
! 11: * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
! 12: *
! 13: * This program is distributed in the hope that it will be useful, but
! 14: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
! 15: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
! 16: * for more details.
! 17: */
! 18:
! 19: #include "dhcp_socket.h"
! 20:
! 21: #include <unistd.h>
! 22: #include <errno.h>
! 23: #include <string.h>
! 24: #include <netinet/in.h>
! 25: #include <netinet/ip.h>
! 26: #include <netinet/udp.h>
! 27: #include <linux/if_arp.h>
! 28: #include <linux/if_ether.h>
! 29: #include <linux/filter.h>
! 30:
! 31: #include <collections/linked_list.h>
! 32: #include <utils/identification.h>
! 33: #include <threading/mutex.h>
! 34: #include <threading/condvar.h>
! 35: #include <threading/thread.h>
! 36:
! 37: #include <daemon.h>
! 38: #include <processing/jobs/callback_job.h>
! 39:
! 40: #define DHCP_SERVER_PORT 67
! 41: #define DHCP_CLIENT_PORT 68
! 42: #define DHCP_TRIES 5
! 43:
! 44: typedef struct private_dhcp_socket_t private_dhcp_socket_t;
! 45:
! 46: /**
! 47: * Private data of an dhcp_socket_t object.
! 48: */
! 49: struct private_dhcp_socket_t {
! 50:
! 51: /**
! 52: * Public dhcp_socket_t interface.
! 53: */
! 54: dhcp_socket_t public;
! 55:
! 56: /**
! 57: * Random number generator
! 58: */
! 59: rng_t *rng;
! 60:
! 61: /**
! 62: * List of transactions in DISCOVER
! 63: */
! 64: linked_list_t *discover;
! 65:
! 66: /**
! 67: * List of transactions in REQUEST
! 68: */
! 69: linked_list_t *request;
! 70:
! 71: /**
! 72: * List of successfully completed transactions
! 73: */
! 74: linked_list_t *completed;
! 75:
! 76: /**
! 77: * Lock for transactions
! 78: */
! 79: mutex_t *mutex;
! 80:
! 81: /**
! 82: * Condvar to wait for transaction completion
! 83: */
! 84: condvar_t *condvar;
! 85:
! 86: /**
! 87: * Threads waiting in condvar
! 88: */
! 89: int waiting;
! 90:
! 91: /**
! 92: * DHCP send socket
! 93: */
! 94: int send;
! 95:
! 96: /**
! 97: * DHCP receive socket
! 98: */
! 99: int receive;
! 100:
! 101: /**
! 102: * Do we use per-identity or random leases (and MAC addresses)
! 103: */
! 104: bool identity_lease;
! 105:
! 106: /**
! 107: * DHCP server address, or broadcast
! 108: */
! 109: host_t *dst;
! 110:
! 111: /**
! 112: * Force configured destination address
! 113: */
! 114: bool force_dst;
! 115: };
! 116:
! 117: /**
! 118: * DHCP opcode (or BOOTP actually)
! 119: */
! 120: typedef enum {
! 121: BOOTREQUEST = 1,
! 122: BOOTREPLY = 2,
! 123: } dhcp_opcode_t;
! 124:
! 125: /**
! 126: * Some DHCP options used
! 127: */
! 128: typedef enum {
! 129: DHCP_DNS_SERVER = 6,
! 130: DHCP_HOST_NAME = 12,
! 131: DHCP_NBNS_SERVER = 44,
! 132: DHCP_REQUESTED_IP = 50,
! 133: DHCP_MESSAGE_TYPE = 53,
! 134: DHCP_SERVER_ID = 54,
! 135: DHCP_PARAM_REQ_LIST = 55,
! 136: DHCP_CLIENT_ID = 61,
! 137: DHCP_OPTEND = 255,
! 138: } dhcp_option_type_t;
! 139:
! 140: /**
! 141: * DHCP messages types in the DHCP_MESSAGE_TYPE option
! 142: */
! 143: typedef enum {
! 144: DHCP_DISCOVER = 1,
! 145: DHCP_OFFER = 2,
! 146: DHCP_REQUEST = 3,
! 147: DHCP_DECLINE = 4,
! 148: DHCP_ACK = 5,
! 149: DHCP_NAK = 6,
! 150: DHCP_RELEASE = 7,
! 151: DHCP_INFORM = 8,
! 152: } dhcp_message_type_t;
! 153: /**
! 154: * DHCP option encoding, a TLV
! 155: */
! 156: typedef struct __attribute__((packed)) {
! 157: uint8_t type;
! 158: uint8_t len;
! 159: char data[];
! 160: } dhcp_option_t;
! 161:
! 162: /**
! 163: * DHCP message format, with a minimum size options buffer
! 164: */
! 165: typedef struct __attribute__((packed)) {
! 166: uint8_t opcode;
! 167: uint8_t hw_type;
! 168: uint8_t hw_addr_len;
! 169: uint8_t hop_count;
! 170: uint32_t transaction_id;
! 171: uint16_t number_of_seconds;
! 172: uint16_t flags;
! 173: uint32_t client_address;
! 174: uint32_t your_address;
! 175: uint32_t server_address;
! 176: uint32_t gateway_address;
! 177: char client_hw_addr[6];
! 178: char client_hw_padding[10];
! 179: char server_hostname[64];
! 180: char boot_filename[128];
! 181: uint32_t magic_cookie;
! 182: u_char options[308];
! 183: } dhcp_t;
! 184:
! 185: /**
! 186: * Check if the given address equals the broadcast address
! 187: */
! 188: static inline bool is_broadcast(host_t *host)
! 189: {
! 190: chunk_t broadcast = chunk_from_chars(0xFF,0xFF,0xFF,0xFF);
! 191:
! 192: return chunk_equals(broadcast, host->get_address(host));
! 193: }
! 194:
! 195: /**
! 196: * Prepare a DHCP message for a given transaction
! 197: */
! 198: static int prepare_dhcp(private_dhcp_socket_t *this,
! 199: dhcp_transaction_t *transaction,
! 200: dhcp_message_type_t type, dhcp_t *dhcp)
! 201: {
! 202: chunk_t chunk;
! 203: identification_t *identity;
! 204: dhcp_option_t *option;
! 205: int optlen = 0, remaining;
! 206: host_t *src;
! 207: uint32_t id;
! 208:
! 209: memset(dhcp, 0, sizeof(*dhcp));
! 210: dhcp->opcode = BOOTREQUEST;
! 211: dhcp->hw_type = ARPHRD_ETHER;
! 212: dhcp->hw_addr_len = 6;
! 213: dhcp->transaction_id = transaction->get_id(transaction);
! 214: if (is_broadcast(this->dst))
! 215: {
! 216: /* Set broadcast flag to get broadcasted replies, as we actually
! 217: * do not own the MAC we request an address for. */
! 218: dhcp->flags = htons(0x8000);
! 219: /* TODO: send with 0.0.0.0 source address */
! 220: }
! 221: else
! 222: {
! 223: /* act as relay agent */
! 224: src = charon->kernel->get_source_addr(charon->kernel, this->dst, NULL);
! 225: if (src)
! 226: {
! 227: memcpy(&dhcp->gateway_address, src->get_address(src).ptr,
! 228: sizeof(dhcp->gateway_address));
! 229: src->destroy(src);
! 230: }
! 231: }
! 232:
! 233: identity = transaction->get_identity(transaction);
! 234: chunk = identity->get_encoding(identity);
! 235: /* magic bytes, a locally administered unicast MAC */
! 236: dhcp->client_hw_addr[0] = 0x7A;
! 237: dhcp->client_hw_addr[1] = 0xA7;
! 238: /* with ID specific postfix */
! 239: if (this->identity_lease)
! 240: {
! 241: id = htonl(chunk_hash_static(chunk));
! 242: }
! 243: else
! 244: {
! 245: id = transaction->get_id(transaction);
! 246: }
! 247: memcpy(&dhcp->client_hw_addr[2], &id, sizeof(id));
! 248:
! 249: dhcp->magic_cookie = htonl(0x63825363);
! 250:
! 251: option = (dhcp_option_t*)&dhcp->options[optlen];
! 252: option->type = DHCP_MESSAGE_TYPE;
! 253: option->len = 1;
! 254: option->data[0] = type;
! 255: optlen += sizeof(dhcp_option_t) + option->len;
! 256:
! 257: /* the REQUEST message has the most static overhead in the 'options' field
! 258: * with 17 bytes */
! 259: remaining = sizeof(dhcp->options) - optlen - 17;
! 260:
! 261: if (identity->get_type(identity) == ID_FQDN)
! 262: {
! 263: option = (dhcp_option_t*)&dhcp->options[optlen];
! 264: option->type = DHCP_HOST_NAME;
! 265: option->len = min(min(chunk.len, remaining-sizeof(dhcp_option_t)), 255);
! 266: memcpy(option->data, chunk.ptr, option->len);
! 267: optlen += sizeof(dhcp_option_t) + option->len;
! 268: remaining -= sizeof(dhcp_option_t) + option->len;
! 269: }
! 270:
! 271: if (this->identity_lease &&
! 272: remaining >= sizeof(dhcp_option_t) + 2)
! 273: {
! 274: option = (dhcp_option_t*)&dhcp->options[optlen];
! 275: option->type = DHCP_CLIENT_ID;
! 276: option->len = min(min(chunk.len, remaining-sizeof(dhcp_option_t)), 255);
! 277: memcpy(option->data, chunk.ptr, option->len);
! 278: optlen += sizeof(dhcp_option_t) + option->len;
! 279: }
! 280: return optlen;
! 281: }
! 282:
! 283: /**
! 284: * Send a DHCP message with given options length
! 285: */
! 286: static bool send_dhcp(private_dhcp_socket_t *this,
! 287: dhcp_transaction_t *transaction, dhcp_t *dhcp, int optlen)
! 288: {
! 289: host_t *dst;
! 290: ssize_t len;
! 291:
! 292: dst = transaction->get_server(transaction);
! 293: if (!dst || this->force_dst)
! 294: {
! 295: dst = this->dst;
! 296: }
! 297: len = offsetof(dhcp_t, magic_cookie) + optlen + 4;
! 298: return sendto(this->send, dhcp, len, 0, dst->get_sockaddr(dst),
! 299: *dst->get_sockaddr_len(dst)) == len;
! 300: }
! 301:
! 302: /**
! 303: * Send DHCP discover using a given transaction
! 304: */
! 305: static bool discover(private_dhcp_socket_t *this,
! 306: dhcp_transaction_t *transaction)
! 307: {
! 308: dhcp_option_t *option;
! 309: dhcp_t dhcp;
! 310: int optlen;
! 311:
! 312: optlen = prepare_dhcp(this, transaction, DHCP_DISCOVER, &dhcp);
! 313:
! 314: DBG1(DBG_CFG, "sending DHCP DISCOVER to %H", this->dst);
! 315:
! 316: option = (dhcp_option_t*)&dhcp.options[optlen];
! 317: option->type = DHCP_PARAM_REQ_LIST;
! 318: option->len = 2;
! 319: option->data[0] = DHCP_DNS_SERVER;
! 320: option->data[1] = DHCP_NBNS_SERVER;
! 321: optlen += sizeof(dhcp_option_t) + option->len;
! 322:
! 323: dhcp.options[optlen++] = DHCP_OPTEND;
! 324:
! 325: if (!send_dhcp(this, transaction, &dhcp, optlen))
! 326: {
! 327: DBG1(DBG_CFG, "sending DHCP DISCOVER failed: %s", strerror(errno));
! 328: return FALSE;
! 329: }
! 330: return TRUE;
! 331: }
! 332:
! 333: /**
! 334: * Send DHCP request using a given transaction
! 335: */
! 336: static bool request(private_dhcp_socket_t *this,
! 337: dhcp_transaction_t *transaction)
! 338: {
! 339: dhcp_option_t *option;
! 340: dhcp_t dhcp;
! 341: host_t *offer, *server;
! 342: chunk_t chunk;
! 343: int optlen;
! 344:
! 345: optlen = prepare_dhcp(this, transaction, DHCP_REQUEST, &dhcp);
! 346:
! 347: offer = transaction->get_address(transaction);
! 348: server = transaction->get_server(transaction);
! 349: if (!offer || !server)
! 350: {
! 351: return FALSE;
! 352: }
! 353: DBG1(DBG_CFG, "sending DHCP REQUEST for %H to %H", offer, server);
! 354:
! 355: option = (dhcp_option_t*)&dhcp.options[optlen];
! 356: option->type = DHCP_REQUESTED_IP;
! 357: option->len = 4;
! 358: chunk = offer->get_address(offer);
! 359: memcpy(option->data, chunk.ptr, min(chunk.len, option->len));
! 360: optlen += sizeof(dhcp_option_t) + option->len;
! 361:
! 362: option = (dhcp_option_t*)&dhcp.options[optlen];
! 363: option->type = DHCP_SERVER_ID;
! 364: option->len = 4;
! 365: chunk = server->get_address(server);
! 366: memcpy(option->data, chunk.ptr, min(chunk.len, option->len));
! 367: optlen += sizeof(dhcp_option_t) + option->len;
! 368:
! 369: option = (dhcp_option_t*)&dhcp.options[optlen];
! 370: option->type = DHCP_PARAM_REQ_LIST;
! 371: option->len = 2;
! 372: option->data[0] = DHCP_DNS_SERVER;
! 373: option->data[1] = DHCP_NBNS_SERVER;
! 374: optlen += sizeof(dhcp_option_t) + option->len;
! 375:
! 376: dhcp.options[optlen++] = DHCP_OPTEND;
! 377:
! 378: if (!send_dhcp(this, transaction, &dhcp, optlen))
! 379: {
! 380: DBG1(DBG_CFG, "sending DHCP REQUEST failed: %s", strerror(errno));
! 381: return FALSE;
! 382: }
! 383: return TRUE;
! 384: }
! 385:
! 386: METHOD(dhcp_socket_t, enroll, dhcp_transaction_t*,
! 387: private_dhcp_socket_t *this, identification_t *identity)
! 388: {
! 389: dhcp_transaction_t *transaction;
! 390: uint32_t id;
! 391: int try;
! 392:
! 393: if (!this->rng->get_bytes(this->rng, sizeof(id), (uint8_t*)&id))
! 394: {
! 395: DBG1(DBG_CFG, "DHCP DISCOVER failed, no transaction ID");
! 396: return NULL;
! 397: }
! 398: transaction = dhcp_transaction_create(id, identity);
! 399:
! 400: this->mutex->lock(this->mutex);
! 401: this->discover->insert_last(this->discover, transaction);
! 402: try = 1;
! 403: while (try <= DHCP_TRIES && discover(this, transaction))
! 404: {
! 405: if (!this->condvar->timed_wait(this->condvar, this->mutex, 1000 * try) &&
! 406: this->request->find_first(this->request, NULL, (void**)&transaction))
! 407: {
! 408: break;
! 409: }
! 410: try++;
! 411: }
! 412: if (this->discover->remove(this->discover, transaction, NULL))
! 413: { /* no OFFER received */
! 414: this->mutex->unlock(this->mutex);
! 415: transaction->destroy(transaction);
! 416: DBG1(DBG_CFG, "DHCP DISCOVER timed out");
! 417: return NULL;
! 418: }
! 419:
! 420: try = 1;
! 421: while (try <= DHCP_TRIES && request(this, transaction))
! 422: {
! 423: if (!this->condvar->timed_wait(this->condvar, this->mutex, 1000 * try) &&
! 424: this->completed->remove(this->completed, transaction, NULL))
! 425: {
! 426: break;
! 427: }
! 428: try++;
! 429: }
! 430: if (this->request->remove(this->request, transaction, NULL))
! 431: { /* no ACK received */
! 432: this->mutex->unlock(this->mutex);
! 433: transaction->destroy(transaction);
! 434: DBG1(DBG_CFG, "DHCP REQUEST timed out");
! 435: return NULL;
! 436: }
! 437: this->mutex->unlock(this->mutex);
! 438:
! 439: return transaction;
! 440: }
! 441:
! 442: METHOD(dhcp_socket_t, release, void,
! 443: private_dhcp_socket_t *this, dhcp_transaction_t *transaction)
! 444: {
! 445: dhcp_option_t *option;
! 446: dhcp_t dhcp;
! 447: host_t *release, *server;
! 448: chunk_t chunk;
! 449: int optlen;
! 450:
! 451: optlen = prepare_dhcp(this, transaction, DHCP_RELEASE, &dhcp);
! 452:
! 453: release = transaction->get_address(transaction);
! 454: server = transaction->get_server(transaction);
! 455: if (!release || !server)
! 456: {
! 457: return;
! 458: }
! 459: DBG1(DBG_CFG, "sending DHCP RELEASE for %H to %H", release, server);
! 460:
! 461: chunk = release->get_address(release);
! 462: memcpy((char*)&dhcp.client_address, chunk.ptr,
! 463: min(chunk.len, sizeof(dhcp.client_address)));
! 464:
! 465: option = (dhcp_option_t*)&dhcp.options[optlen];
! 466: option->type = DHCP_SERVER_ID;
! 467: option->len = 4;
! 468: chunk = server->get_address(server);
! 469: memcpy(option->data, chunk.ptr, min(chunk.len, option->len));
! 470: optlen += sizeof(dhcp_option_t) + option->len;
! 471:
! 472: dhcp.options[optlen++] = DHCP_OPTEND;
! 473:
! 474: if (!send_dhcp(this, transaction, &dhcp, optlen))
! 475: {
! 476: DBG1(DBG_CFG, "sending DHCP RELEASE failed: %s", strerror(errno));
! 477: }
! 478: }
! 479:
! 480: /**
! 481: * Handle a DHCP OFFER
! 482: */
! 483: static void handle_offer(private_dhcp_socket_t *this, dhcp_t *dhcp, int optlen)
! 484: {
! 485: dhcp_transaction_t *transaction = NULL;
! 486: enumerator_t *enumerator;
! 487: host_t *offer, *server = NULL;
! 488:
! 489: offer = host_create_from_chunk(AF_INET,
! 490: chunk_from_thing(dhcp->your_address), 0);
! 491:
! 492: if (offer->is_anyaddr(offer))
! 493: {
! 494: server = host_create_from_chunk(AF_INET,
! 495: chunk_from_thing(dhcp->server_address), 0);
! 496: DBG1(DBG_CFG, "ignoring DHCP OFFER %+H from %H", offer, server);
! 497: server->destroy(server);
! 498: offer->destroy(offer);
! 499: return;
! 500: }
! 501:
! 502: this->mutex->lock(this->mutex);
! 503: enumerator = this->discover->create_enumerator(this->discover);
! 504: while (enumerator->enumerate(enumerator, &transaction))
! 505: {
! 506: if (transaction->get_id(transaction) == dhcp->transaction_id)
! 507: {
! 508: this->discover->remove_at(this->discover, enumerator);
! 509: this->request->insert_last(this->request, transaction);
! 510: break;
! 511: }
! 512: }
! 513: enumerator->destroy(enumerator);
! 514:
! 515: if (transaction)
! 516: {
! 517: int optsize, optpos = 0, pos;
! 518: dhcp_option_t *option;
! 519:
! 520: while (optlen > sizeof(dhcp_option_t))
! 521: {
! 522: option = (dhcp_option_t*)&dhcp->options[optpos];
! 523: optsize = sizeof(dhcp_option_t) + option->len;
! 524: if (option->type == DHCP_OPTEND || optlen < optsize)
! 525: {
! 526: break;
! 527: }
! 528: if (option->type == DHCP_DNS_SERVER ||
! 529: option->type == DHCP_NBNS_SERVER)
! 530: {
! 531: for (pos = 0; pos + 4 <= option->len; pos += 4)
! 532: {
! 533: transaction->add_attribute(transaction, option->type ==
! 534: DHCP_DNS_SERVER ? INTERNAL_IP4_DNS : INTERNAL_IP4_NBNS,
! 535: chunk_create((char*)&option->data[pos], 4));
! 536: }
! 537: }
! 538: if (!server && option->type == DHCP_SERVER_ID && option->len == 4)
! 539: {
! 540: server = host_create_from_chunk(AF_INET,
! 541: chunk_create(option->data, 4), DHCP_SERVER_PORT);
! 542: }
! 543: optlen -= optsize;
! 544: optpos += optsize;
! 545: }
! 546: if (!server)
! 547: {
! 548: server = host_create_from_chunk(AF_INET,
! 549: chunk_from_thing(dhcp->server_address), DHCP_SERVER_PORT);
! 550: }
! 551: DBG1(DBG_CFG, "received DHCP OFFER %H from %H", offer, server);
! 552: transaction->set_address(transaction, offer->clone(offer));
! 553: transaction->set_server(transaction, server);
! 554: }
! 555: this->mutex->unlock(this->mutex);
! 556: this->condvar->broadcast(this->condvar);
! 557: offer->destroy(offer);
! 558: }
! 559:
! 560: /**
! 561: * Handle a DHCP ACK
! 562: */
! 563: static void handle_ack(private_dhcp_socket_t *this, dhcp_t *dhcp, int optlen)
! 564: {
! 565: dhcp_transaction_t *transaction;
! 566: enumerator_t *enumerator;
! 567: host_t *offer;
! 568:
! 569: offer = host_create_from_chunk(AF_INET,
! 570: chunk_from_thing(dhcp->your_address), 0);
! 571:
! 572: this->mutex->lock(this->mutex);
! 573: enumerator = this->request->create_enumerator(this->request);
! 574: while (enumerator->enumerate(enumerator, &transaction))
! 575: {
! 576: if (transaction->get_id(transaction) == dhcp->transaction_id)
! 577: {
! 578: DBG1(DBG_CFG, "received DHCP ACK for %H", offer);
! 579: this->request->remove_at(this->request, enumerator);
! 580: this->completed->insert_last(this->completed, transaction);
! 581: break;
! 582: }
! 583: }
! 584: enumerator->destroy(enumerator);
! 585: this->mutex->unlock(this->mutex);
! 586: this->condvar->broadcast(this->condvar);
! 587: offer->destroy(offer);
! 588: }
! 589:
! 590: /**
! 591: * Receive DHCP responses
! 592: */
! 593: static bool receive_dhcp(private_dhcp_socket_t *this, int fd,
! 594: watcher_event_t event)
! 595: {
! 596: struct sockaddr_ll addr;
! 597: socklen_t addr_len = sizeof(addr);
! 598: struct __attribute__((packed)) {
! 599: struct iphdr ip;
! 600: struct udphdr udp;
! 601: dhcp_t dhcp;
! 602: } packet;
! 603: int optlen, origoptlen, optsize, optpos = 0;
! 604: ssize_t len;
! 605: dhcp_option_t *option;
! 606:
! 607: len = recvfrom(fd, &packet, sizeof(packet), MSG_DONTWAIT,
! 608: (struct sockaddr*)&addr, &addr_len);
! 609:
! 610: if (len >= sizeof(struct iphdr) + sizeof(struct udphdr) +
! 611: offsetof(dhcp_t, options))
! 612: {
! 613: origoptlen = optlen = len - sizeof(struct iphdr) +
! 614: sizeof(struct udphdr) + offsetof(dhcp_t, options);
! 615: while (optlen > sizeof(dhcp_option_t))
! 616: {
! 617: option = (dhcp_option_t*)&packet.dhcp.options[optpos];
! 618: optsize = sizeof(dhcp_option_t) + option->len;
! 619: if (option->type == DHCP_OPTEND || optlen < optsize)
! 620: {
! 621: break;
! 622: }
! 623: if (option->type == DHCP_MESSAGE_TYPE && option->len == 1)
! 624: {
! 625: switch (option->data[0])
! 626: {
! 627: case DHCP_OFFER:
! 628: handle_offer(this, &packet.dhcp, origoptlen);
! 629: break;
! 630: case DHCP_ACK:
! 631: handle_ack(this, &packet.dhcp, origoptlen);
! 632: default:
! 633: break;
! 634: }
! 635: break;
! 636: }
! 637: optlen -= optsize;
! 638: optpos += optsize;
! 639: }
! 640: }
! 641: return TRUE;
! 642: }
! 643:
! 644: METHOD(dhcp_socket_t, destroy, void,
! 645: private_dhcp_socket_t *this)
! 646: {
! 647: while (this->waiting)
! 648: {
! 649: this->condvar->signal(this->condvar);
! 650: }
! 651: if (this->send > 0)
! 652: {
! 653: close(this->send);
! 654: }
! 655: if (this->receive > 0)
! 656: {
! 657: lib->watcher->remove(lib->watcher, this->receive);
! 658: close(this->receive);
! 659: }
! 660: this->mutex->destroy(this->mutex);
! 661: this->condvar->destroy(this->condvar);
! 662: this->discover->destroy_offset(this->discover,
! 663: offsetof(dhcp_transaction_t, destroy));
! 664: this->request->destroy_offset(this->request,
! 665: offsetof(dhcp_transaction_t, destroy));
! 666: this->completed->destroy_offset(this->completed,
! 667: offsetof(dhcp_transaction_t, destroy));
! 668: DESTROY_IF(this->rng);
! 669: DESTROY_IF(this->dst);
! 670: free(this);
! 671: }
! 672:
! 673: /**
! 674: * Bind a socket to a particular interface name
! 675: */
! 676: static bool bind_to_device(int fd, char *iface)
! 677: {
! 678: struct ifreq ifreq;
! 679:
! 680: if (strlen(iface) > sizeof(ifreq.ifr_name))
! 681: {
! 682: DBG1(DBG_CFG, "name for DHCP interface too long: '%s'", iface);
! 683: return FALSE;
! 684: }
! 685: memcpy(ifreq.ifr_name, iface, sizeof(ifreq.ifr_name));
! 686: if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq)))
! 687: {
! 688: DBG1(DBG_CFG, "binding DHCP socket to '%s' failed: %s",
! 689: iface, strerror(errno));
! 690: return FALSE;
! 691: }
! 692: return TRUE;
! 693: }
! 694:
! 695: /**
! 696: * See header
! 697: */
! 698: dhcp_socket_t *dhcp_socket_create()
! 699: {
! 700: private_dhcp_socket_t *this;
! 701: struct sockaddr_in src = {
! 702: .sin_family = AF_INET,
! 703: .sin_port = htons(DHCP_CLIENT_PORT),
! 704: .sin_addr = {
! 705: .s_addr = INADDR_ANY,
! 706: },
! 707: };
! 708: char *iface;
! 709: int on = 1, rcvbuf = 0;
! 710: struct sock_filter dhcp_filter_code[] = {
! 711: BPF_STMT(BPF_LD+BPF_B+BPF_ABS,
! 712: offsetof(struct iphdr, protocol)),
! 713: BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_UDP, 0, 16),
! 714: BPF_STMT(BPF_LD+BPF_H+BPF_ABS, sizeof(struct iphdr) +
! 715: offsetof(struct udphdr, source)),
! 716: BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, DHCP_SERVER_PORT, 0, 14),
! 717: BPF_STMT(BPF_LD+BPF_H+BPF_ABS, sizeof(struct iphdr) +
! 718: offsetof(struct udphdr, dest)),
! 719: BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, DHCP_CLIENT_PORT, 2, 0),
! 720: BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, DHCP_SERVER_PORT, 1, 0),
! 721: BPF_JUMP(BPF_JMP+BPF_JA, 10, 0, 0),
! 722: BPF_STMT(BPF_LD+BPF_B+BPF_ABS, sizeof(struct iphdr) +
! 723: sizeof(struct udphdr) + offsetof(dhcp_t, opcode)),
! 724: BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, BOOTREPLY, 0, 8),
! 725: BPF_STMT(BPF_LD+BPF_B+BPF_ABS, sizeof(struct iphdr) +
! 726: sizeof(struct udphdr) + offsetof(dhcp_t, hw_type)),
! 727: BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARPHRD_ETHER, 0, 6),
! 728: BPF_STMT(BPF_LD+BPF_B+BPF_ABS, sizeof(struct iphdr) +
! 729: sizeof(struct udphdr) + offsetof(dhcp_t, hw_addr_len)),
! 730: BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 6, 0, 4),
! 731: BPF_STMT(BPF_LD+BPF_W+BPF_ABS, sizeof(struct iphdr) +
! 732: sizeof(struct udphdr) + offsetof(dhcp_t, magic_cookie)),
! 733: BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0x63825363, 0, 2),
! 734: BPF_STMT(BPF_LD+BPF_W+BPF_LEN, 0),
! 735: BPF_STMT(BPF_RET+BPF_A, 0),
! 736: BPF_STMT(BPF_RET+BPF_K, 0),
! 737: };
! 738: struct sock_fprog dhcp_filter = {
! 739: sizeof(dhcp_filter_code) / sizeof(struct sock_filter),
! 740: dhcp_filter_code,
! 741: };
! 742:
! 743: INIT(this,
! 744: .public = {
! 745: .enroll = _enroll,
! 746: .release = _release,
! 747: .destroy = _destroy,
! 748: },
! 749: .rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK),
! 750: .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
! 751: .condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
! 752: .discover = linked_list_create(),
! 753: .request = linked_list_create(),
! 754: .completed = linked_list_create(),
! 755: );
! 756:
! 757: if (!this->rng)
! 758: {
! 759: DBG1(DBG_CFG, "unable to create RNG");
! 760: destroy(this);
! 761: return NULL;
! 762: }
! 763: this->identity_lease = lib->settings->get_bool(lib->settings,
! 764: "%s.plugins.dhcp.identity_lease", FALSE,
! 765: lib->ns);
! 766: this->force_dst = lib->settings->get_str(lib->settings,
! 767: "%s.plugins.dhcp.force_server_address", FALSE,
! 768: lib->ns);
! 769: this->dst = host_create_from_string(lib->settings->get_str(lib->settings,
! 770: "%s.plugins.dhcp.server", "255.255.255.255",
! 771: lib->ns), DHCP_SERVER_PORT);
! 772: iface = lib->settings->get_str(lib->settings, "%s.plugins.dhcp.interface",
! 773: NULL, lib->ns);
! 774: if (!this->dst)
! 775: {
! 776: DBG1(DBG_CFG, "configured DHCP server address invalid");
! 777: destroy(this);
! 778: return NULL;
! 779: }
! 780:
! 781: this->send = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
! 782: if (this->send == -1)
! 783: {
! 784: DBG1(DBG_CFG, "unable to create DHCP send socket: %s", strerror(errno));
! 785: destroy(this);
! 786: return NULL;
! 787: }
! 788: if (setsockopt(this->send, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
! 789: {
! 790: DBG1(DBG_CFG, "unable to reuse DHCP socket address: %s", strerror(errno));
! 791: destroy(this);
! 792: return NULL;
! 793: }
! 794: if (setsockopt(this->send, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) == -1)
! 795: {
! 796: DBG1(DBG_CFG, "unable to broadcast on DHCP socket: %s", strerror(errno));
! 797: destroy(this);
! 798: return NULL;
! 799: }
! 800: /* we won't read any data from this socket, so reduce the buffer to save
! 801: * some memory (there is some minimum, still try 0, though).
! 802: * note that we might steal some packets from other processes if e.g. a DHCP
! 803: * client (or server) is running on the same host, but by reducing the
! 804: * buffer size the impact should be minimized */
! 805: if (setsockopt(this->send, SOL_SOCKET, SO_RCVBUF, &rcvbuf,
! 806: sizeof(rcvbuf)) == -1)
! 807: {
! 808: DBG1(DBG_CFG, "unable to reduce receive buffer on DHCP send socket: %s",
! 809: strerror(errno));
! 810: destroy(this);
! 811: return NULL;
! 812: }
! 813: if (!is_broadcast(this->dst) &&
! 814: lib->settings->get_bool(lib->settings,
! 815: "%s.plugins.dhcp.use_server_port", FALSE,
! 816: lib->ns))
! 817: {
! 818: /* when setting giaddr (which we do when we don't broadcast), the server
! 819: * should respond to the server port on that IP, according to RFC 2131,
! 820: * section 4.1. while we do receive such messages via raw socket, the
! 821: * kernel will respond with an ICMP port unreachable if there is no
! 822: * socket bound to that port, which might be problematic with certain
! 823: * DHCP servers. instead of opening an additional socket, that we don't
! 824: * actually use, we can also just send our requests from port 67.
! 825: * we don't do this by default, as it might cause conflicts with DHCP
! 826: * servers running on the same host */
! 827: src.sin_port = htons(DHCP_SERVER_PORT);
! 828: }
! 829: if (bind(this->send, (struct sockaddr*)&src, sizeof(src)) == -1)
! 830: {
! 831: DBG1(DBG_CFG, "unable to bind DHCP send socket: %s", strerror(errno));
! 832: destroy(this);
! 833: return NULL;
! 834: }
! 835:
! 836: this->receive = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
! 837: if (this->receive == -1)
! 838: {
! 839: DBG1(DBG_NET, "opening DHCP receive socket failed: %s", strerror(errno));
! 840: destroy(this);
! 841: return NULL;
! 842: }
! 843: if (setsockopt(this->receive, SOL_SOCKET, SO_ATTACH_FILTER,
! 844: &dhcp_filter, sizeof(dhcp_filter)) < 0)
! 845: {
! 846: DBG1(DBG_CFG, "installing DHCP socket filter failed: %s",
! 847: strerror(errno));
! 848: destroy(this);
! 849: return NULL;
! 850: }
! 851: if (iface)
! 852: {
! 853: if (!bind_to_device(this->send, iface) ||
! 854: !bind_to_device(this->receive, iface))
! 855: {
! 856: destroy(this);
! 857: return NULL;
! 858: }
! 859: }
! 860:
! 861: lib->watcher->add(lib->watcher, this->receive, WATCHER_READ,
! 862: (watcher_cb_t)receive_dhcp, this);
! 863:
! 864: return &this->public;
! 865: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>