qemu/slirp/src/dhcpv6.c
<<
>>
Prefs
   1/* SPDX-License-Identifier: BSD-3-Clause */
   2/*
   3 * SLIRP stateless DHCPv6
   4 *
   5 * We only support stateless DHCPv6, e.g. for network booting.
   6 * See RFC 3315, RFC 3736, RFC 3646 and RFC 5970 for details.
   7 *
   8 * Copyright 2016 Thomas Huth, Red Hat Inc.
   9 *
  10 * Redistribution and use in source and binary forms, with or without
  11 * modification, are permitted provided that the following conditions
  12 * are met:
  13 *
  14 * 1. Redistributions of source code must retain the above
  15 * copyright notice, this list of conditions and the following
  16 * disclaimer.
  17 *
  18 * 2. Redistributions in binary form must reproduce the above
  19 * copyright notice, this list of conditions and the following
  20 * disclaimer in the documentation and/or other materials provided
  21 * with the distribution.
  22 *
  23 * 3. Neither the name of the copyright holder nor the names of its
  24 * contributors may be used to endorse or promote products derived
  25 * from this software without specific prior written permission.
  26 *
  27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  28 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  29 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  30 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  31 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
  32 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  33 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  34 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  36 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  37 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  38 * OF THE POSSIBILITY OF SUCH DAMAGE.
  39 */
  40
  41#include "slirp.h"
  42#include "dhcpv6.h"
  43
  44/* DHCPv6 message types */
  45#define MSGTYPE_REPLY        7
  46#define MSGTYPE_INFO_REQUEST 11
  47
  48/* DHCPv6 option types */
  49#define OPTION_CLIENTID      1
  50#define OPTION_IAADDR        5
  51#define OPTION_ORO           6
  52#define OPTION_DNS_SERVERS   23
  53#define OPTION_BOOTFILE_URL  59
  54
  55struct requested_infos {
  56    uint8_t *client_id;
  57    int client_id_len;
  58    bool want_dns;
  59    bool want_boot_url;
  60};
  61
  62/**
  63 * Analyze the info request message sent by the client to see what data it
  64 * provided and what it wants to have. The information is gathered in the
  65 * "requested_infos" struct. Note that client_id (if provided) points into
  66 * the odata region, thus the caller must keep odata valid as long as it
  67 * needs to access the requested_infos struct.
  68 */
  69static int dhcpv6_parse_info_request(Slirp *slirp, uint8_t *odata, int olen,
  70                                     struct requested_infos *ri)
  71{
  72    int i, req_opt;
  73
  74    while (olen > 4) {
  75        /* Parse one option */
  76        int option = odata[0] << 8 | odata[1];
  77        int len = odata[2] << 8 | odata[3];
  78
  79        if (len + 4 > olen) {
  80            slirp->cb->guest_error("Guest sent bad DHCPv6 packet!", slirp->opaque);
  81            return -E2BIG;
  82        }
  83
  84        switch (option) {
  85        case OPTION_IAADDR:
  86            /* According to RFC3315, we must discard requests with IA option */
  87            return -EINVAL;
  88        case OPTION_CLIENTID:
  89            if (len > 256) {
  90                /* Avoid very long IDs which could cause problems later */
  91                return -E2BIG;
  92            }
  93            ri->client_id = odata + 4;
  94            ri->client_id_len = len;
  95            break;
  96        case OPTION_ORO:        /* Option request option */
  97            if (len & 1) {
  98                return -EINVAL;
  99            }
 100            /* Check which options the client wants to have */
 101            for (i = 0; i < len; i += 2) {
 102                req_opt = odata[4 + i] << 8 | odata[4 + i + 1];
 103                switch (req_opt) {
 104                case OPTION_DNS_SERVERS:
 105                    ri->want_dns = true;
 106                    break;
 107                case OPTION_BOOTFILE_URL:
 108                    ri->want_boot_url = true;
 109                    break;
 110                default:
 111                    DEBUG_MISC("dhcpv6: Unsupported option request %d",
 112                               req_opt);
 113                }
 114            }
 115            break;
 116        default:
 117            DEBUG_MISC("dhcpv6 info req: Unsupported option %d, len=%d",
 118                       option, len);
 119        }
 120
 121        odata += len + 4;
 122        olen -= len + 4;
 123    }
 124
 125    return 0;
 126}
 127
 128
 129/**
 130 * Handle information request messages
 131 */
 132static void dhcpv6_info_request(Slirp *slirp, struct sockaddr_in6 *srcsas,
 133                                uint32_t xid, uint8_t *odata, int olen)
 134{
 135    struct requested_infos ri = { NULL };
 136    struct sockaddr_in6 sa6, da6;
 137    struct mbuf *m;
 138    uint8_t *resp;
 139
 140    if (dhcpv6_parse_info_request(slirp, odata, olen, &ri) < 0) {
 141        return;
 142    }
 143
 144    m = m_get(slirp);
 145    if (!m) {
 146        return;
 147    }
 148    memset(m->m_data, 0, m->m_size);
 149    m->m_data += IF_MAXLINKHDR;
 150    resp = (uint8_t *)m->m_data + sizeof(struct ip6) + sizeof(struct udphdr);
 151
 152    /* Fill in response */
 153    *resp++ = MSGTYPE_REPLY;
 154    *resp++ = (uint8_t)(xid >> 16);
 155    *resp++ = (uint8_t)(xid >> 8);
 156    *resp++ = (uint8_t)xid;
 157
 158    if (ri.client_id) {
 159        *resp++ = OPTION_CLIENTID >> 8;         /* option-code high byte */
 160        *resp++ = OPTION_CLIENTID;              /* option-code low byte */
 161        *resp++ = ri.client_id_len >> 8;        /* option-len high byte */
 162        *resp++ = ri.client_id_len;             /* option-len low byte */
 163        memcpy(resp, ri.client_id, ri.client_id_len);
 164        resp += ri.client_id_len;
 165    }
 166    if (ri.want_dns) {
 167        *resp++ = OPTION_DNS_SERVERS >> 8;      /* option-code high byte */
 168        *resp++ = OPTION_DNS_SERVERS;           /* option-code low byte */
 169        *resp++ = 0;                            /* option-len high byte */
 170        *resp++ = 16;                           /* option-len low byte */
 171        memcpy(resp, &slirp->vnameserver_addr6, 16);
 172        resp += 16;
 173    }
 174    if (ri.want_boot_url) {
 175        uint8_t *sa = slirp->vhost_addr6.s6_addr;
 176        int slen, smaxlen;
 177
 178        *resp++ = OPTION_BOOTFILE_URL >> 8;     /* option-code high byte */
 179        *resp++ = OPTION_BOOTFILE_URL;          /* option-code low byte */
 180        smaxlen = (uint8_t *)m->m_data + IF_MTU - (resp + 2);
 181        slen = snprintf((char *)resp + 2, smaxlen,
 182                        "tftp://[%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
 183                                "%02x%02x:%02x%02x:%02x%02x:%02x%02x]/%s",
 184                        sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], sa[6], sa[7],
 185                        sa[8], sa[9], sa[10], sa[11], sa[12], sa[13], sa[14],
 186                        sa[15], slirp->bootp_filename);
 187        slen = MIN(slen, smaxlen);
 188        *resp++ = slen >> 8;                    /* option-len high byte */
 189        *resp++ = slen;                         /* option-len low byte */
 190        resp += slen;
 191    }
 192
 193    sa6.sin6_addr = slirp->vhost_addr6;
 194    sa6.sin6_port = DHCPV6_SERVER_PORT;
 195    da6.sin6_addr = srcsas->sin6_addr;
 196    da6.sin6_port = srcsas->sin6_port;
 197    m->m_data += sizeof(struct ip6) + sizeof(struct udphdr);
 198    m->m_len = resp - (uint8_t *)m->m_data;
 199    udp6_output(NULL, m, &sa6, &da6);
 200}
 201
 202/**
 203 * Handle DHCPv6 messages sent by the client
 204 */
 205void dhcpv6_input(struct sockaddr_in6 *srcsas, struct mbuf *m)
 206{
 207    uint8_t *data = (uint8_t *)m->m_data + sizeof(struct udphdr);
 208    int data_len = m->m_len - sizeof(struct udphdr);
 209    uint32_t xid;
 210
 211    if (data_len < 4) {
 212        return;
 213    }
 214
 215    xid = ntohl(*(uint32_t *)data) & 0xffffff;
 216
 217    switch (data[0]) {
 218    case MSGTYPE_INFO_REQUEST:
 219        dhcpv6_info_request(m->slirp, srcsas, xid, &data[4], data_len - 4);
 220        break;
 221    default:
 222        DEBUG_MISC("dhcpv6_input: Unsupported message type 0x%x", data[0]);
 223    }
 224}
 225