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>