qemu/slirp/dhcpv6.c
<<
>>
Prefs
   1/*
   2 * SLIRP stateless DHCPv6
   3 *
   4 * We only support stateless DHCPv6, e.g. for network booting.
   5 * See RFC 3315, RFC 3736, RFC 3646 and RFC 5970 for details.
   6 *
   7 * Copyright 2016 Thomas Huth, Red Hat Inc.
   8 *
   9 * This program is free software; you can redistribute it and/or modify
  10 * it under the terms of the GNU General Public License as published by
  11 * the Free Software Foundation; either version 2 of the License,
  12 * or (at your option) any later version.
  13 *
  14 * This program is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17 * GNU General Public License for more details.
  18 *
  19 * You should have received a copy of the GNU General Public License
  20 * along with this program; if not, see <http://www.gnu.org/licenses/>.
  21 */
  22
  23#include "qemu/osdep.h"
  24#include "qemu/log.h"
  25#include "slirp.h"
  26#include "dhcpv6.h"
  27
  28/* DHCPv6 message types */
  29#define MSGTYPE_REPLY        7
  30#define MSGTYPE_INFO_REQUEST 11
  31
  32/* DHCPv6 option types */
  33#define OPTION_CLIENTID      1
  34#define OPTION_IAADDR        5
  35#define OPTION_ORO           6
  36#define OPTION_DNS_SERVERS   23
  37#define OPTION_BOOTFILE_URL  59
  38
  39struct requested_infos {
  40    uint8_t *client_id;
  41    int client_id_len;
  42    bool want_dns;
  43    bool want_boot_url;
  44};
  45
  46/**
  47 * Analyze the info request message sent by the client to see what data it
  48 * provided and what it wants to have. The information is gathered in the
  49 * "requested_infos" struct. Note that client_id (if provided) points into
  50 * the odata region, thus the caller must keep odata valid as long as it
  51 * needs to access the requested_infos struct.
  52 */
  53static int dhcpv6_parse_info_request(uint8_t *odata, int olen,
  54                                     struct requested_infos *ri)
  55{
  56    int i, req_opt;
  57
  58    while (olen > 4) {
  59        /* Parse one option */
  60        int option = odata[0] << 8 | odata[1];
  61        int len = odata[2] << 8 | odata[3];
  62
  63        if (len + 4 > olen) {
  64            qemu_log_mask(LOG_GUEST_ERROR, "Guest sent bad DHCPv6 packet!\n");
  65            return -E2BIG;
  66        }
  67
  68        switch (option) {
  69        case OPTION_IAADDR:
  70            /* According to RFC3315, we must discard requests with IA option */
  71            return -EINVAL;
  72        case OPTION_CLIENTID:
  73            if (len > 256) {
  74                /* Avoid very long IDs which could cause problems later */
  75                return -E2BIG;
  76            }
  77            ri->client_id = odata + 4;
  78            ri->client_id_len = len;
  79            break;
  80        case OPTION_ORO:        /* Option request option */
  81            if (len & 1) {
  82                return -EINVAL;
  83            }
  84            /* Check which options the client wants to have */
  85            for (i = 0; i < len; i += 2) {
  86                req_opt = odata[4 + i] << 8 | odata[4 + i + 1];
  87                switch (req_opt) {
  88                case OPTION_DNS_SERVERS:
  89                    ri->want_dns = true;
  90                    break;
  91                case OPTION_BOOTFILE_URL:
  92                    ri->want_boot_url = true;
  93                    break;
  94                default:
  95                    DEBUG_MISC((dfd, "dhcpv6: Unsupported option request %d\n",
  96                                req_opt));
  97                }
  98            }
  99            break;
 100        default:
 101            DEBUG_MISC((dfd, "dhcpv6 info req: Unsupported option %d, len=%d\n",
 102                        option, len));
 103        }
 104
 105        odata += len + 4;
 106        olen -= len + 4;
 107    }
 108
 109    return 0;
 110}
 111
 112
 113/**
 114 * Handle information request messages
 115 */
 116static void dhcpv6_info_request(Slirp *slirp, struct sockaddr_in6 *srcsas,
 117                                uint32_t xid, uint8_t *odata, int olen)
 118{
 119    struct requested_infos ri = { NULL };
 120    struct sockaddr_in6 sa6, da6;
 121    struct mbuf *m;
 122    uint8_t *resp;
 123
 124    if (dhcpv6_parse_info_request(odata, olen, &ri) < 0) {
 125        return;
 126    }
 127
 128    m = m_get(slirp);
 129    if (!m) {
 130        return;
 131    }
 132    memset(m->m_data, 0, m->m_size);
 133    m->m_data += IF_MAXLINKHDR;
 134    resp = (uint8_t *)m->m_data + sizeof(struct ip6) + sizeof(struct udphdr);
 135
 136    /* Fill in response */
 137    *resp++ = MSGTYPE_REPLY;
 138    *resp++ = (uint8_t)(xid >> 16);
 139    *resp++ = (uint8_t)(xid >> 8);
 140    *resp++ = (uint8_t)xid;
 141
 142    if (ri.client_id) {
 143        *resp++ = OPTION_CLIENTID >> 8;         /* option-code high byte */
 144        *resp++ = OPTION_CLIENTID;              /* option-code low byte */
 145        *resp++ = ri.client_id_len >> 8;        /* option-len high byte */
 146        *resp++ = ri.client_id_len;             /* option-len low byte */
 147        memcpy(resp, ri.client_id, ri.client_id_len);
 148        resp += ri.client_id_len;
 149    }
 150    if (ri.want_dns) {
 151        *resp++ = OPTION_DNS_SERVERS >> 8;      /* option-code high byte */
 152        *resp++ = OPTION_DNS_SERVERS;           /* option-code low byte */
 153        *resp++ = 0;                            /* option-len high byte */
 154        *resp++ = 16;                           /* option-len low byte */
 155        memcpy(resp, &slirp->vnameserver_addr6, 16);
 156        resp += 16;
 157    }
 158    if (ri.want_boot_url) {
 159        uint8_t *sa = slirp->vhost_addr6.s6_addr;
 160        int slen, smaxlen;
 161
 162        *resp++ = OPTION_BOOTFILE_URL >> 8;     /* option-code high byte */
 163        *resp++ = OPTION_BOOTFILE_URL;          /* option-code low byte */
 164        smaxlen = (uint8_t *)m->m_data + IF_MTU - (resp + 2);
 165        slen = snprintf((char *)resp + 2, smaxlen,
 166                        "tftp://[%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
 167                                "%02x%02x:%02x%02x:%02x%02x:%02x%02x]/%s",
 168                        sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], sa[6], sa[7],
 169                        sa[8], sa[9], sa[10], sa[11], sa[12], sa[13], sa[14],
 170                        sa[15], slirp->bootp_filename);
 171        slen = MIN(slen, smaxlen);
 172        *resp++ = slen >> 8;                    /* option-len high byte */
 173        *resp++ = slen;                         /* option-len low byte */
 174        resp += slen;
 175    }
 176
 177    sa6.sin6_addr = slirp->vhost_addr6;
 178    sa6.sin6_port = DHCPV6_SERVER_PORT;
 179    da6.sin6_addr = srcsas->sin6_addr;
 180    da6.sin6_port = srcsas->sin6_port;
 181    m->m_data += sizeof(struct ip6) + sizeof(struct udphdr);
 182    m->m_len = resp - (uint8_t *)m->m_data;
 183    udp6_output(NULL, m, &sa6, &da6);
 184}
 185
 186/**
 187 * Handle DHCPv6 messages sent by the client
 188 */
 189void dhcpv6_input(struct sockaddr_in6 *srcsas, struct mbuf *m)
 190{
 191    uint8_t *data = (uint8_t *)m->m_data + sizeof(struct udphdr);
 192    int data_len = m->m_len - sizeof(struct udphdr);
 193    uint32_t xid;
 194
 195    if (data_len < 4) {
 196        return;
 197    }
 198
 199    xid = ntohl(*(uint32_t *)data) & 0xffffff;
 200
 201    switch (data[0]) {
 202    case MSGTYPE_INFO_REQUEST:
 203        dhcpv6_info_request(m->slirp, srcsas, xid, &data[4], data_len - 4);
 204        break;
 205    default:
 206        DEBUG_MISC((dfd, "dhcpv6_input: Unsupported message type 0x%x\n",
 207                    data[0]));
 208    }
 209}
 210