Hello, Thomas Huth, on Tue 28 Jun 2016 12:48:31 +0200, wrote: > Provide basic support for stateless DHCPv6 (see RFC 3736) so > that guests can also automatically boot via IPv6 with SLIRP > (for IPv6 network booting, see RFC 5970 for details). > > Tested with: > > qemu-system-ppc64 -nographic -vga none -boot n -net nic \ > -net user,ipv6=yes,ipv4=no,tftp=/path/to/tftp,bootfile=ppc64.img > > Signed-off-by: Thomas Huth <th...@redhat.com>
Pushed to my tree, thanks! Samuel > --- > v2: > - Addressed review comments from Samuel for v1 > - Moved the ALLDHCP_MULTICAST definition to dhcpv6.h instead of ip6.h > - Fixed a bug in the OPTION_ORO parsing (the index was not calculated > right) > > slirp/Makefile.objs | 2 +- > slirp/dhcpv6.c | 209 > ++++++++++++++++++++++++++++++++++++++++++++++++++++ > slirp/dhcpv6.h | 22 ++++++ > slirp/udp6.c | 13 +++- > 4 files changed, 244 insertions(+), 2 deletions(-) > create mode 100644 slirp/dhcpv6.c > create mode 100644 slirp/dhcpv6.h > > diff --git a/slirp/Makefile.objs b/slirp/Makefile.objs > index 6748e4f..1baa1f1 100644 > --- a/slirp/Makefile.objs > +++ b/slirp/Makefile.objs > @@ -1,5 +1,5 @@ > common-obj-y = cksum.o if.o ip_icmp.o ip6_icmp.o ip6_input.o ip6_output.o \ > - ip_input.o ip_output.o dnssearch.o > + ip_input.o ip_output.o dnssearch.o dhcpv6.o > common-obj-y += slirp.o mbuf.o misc.o sbuf.o socket.o tcp_input.o > tcp_output.o > common-obj-y += tcp_subr.o tcp_timer.o udp.o udp6.o bootp.o tftp.o > arp_table.o \ > ndp_table.o > diff --git a/slirp/dhcpv6.c b/slirp/dhcpv6.c > new file mode 100644 > index 0000000..02c51c7 > --- /dev/null > +++ b/slirp/dhcpv6.c > @@ -0,0 +1,209 @@ > +/* > + * SLIRP stateless DHCPv6 > + * > + * We only support stateless DHCPv6, e.g. for network booting. > + * See RFC 3315, RFC 3736, RFC 3646 and RFC 5970 for details. > + * > + * Copyright 2016 Thomas Huth, Red Hat Inc. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, > + * or (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, see <http://www.gnu.org/licenses/>. > + */ > + > +#include "qemu/osdep.h" > +#include "qemu/log.h" > +#include "slirp.h" > +#include "dhcpv6.h" > + > +/* DHCPv6 message types */ > +#define MSGTYPE_REPLY 7 > +#define MSGTYPE_INFO_REQUEST 11 > + > +/* DHCPv6 option types */ > +#define OPTION_CLIENTID 1 > +#define OPTION_IAADDR 5 > +#define OPTION_ORO 6 > +#define OPTION_DNS_SERVERS 23 > +#define OPTION_BOOTFILE_URL 59 > + > +struct requested_infos { > + uint8_t *client_id; > + int client_id_len; > + bool want_dns; > + bool want_boot_url; > +}; > + > +/** > + * Analyze the info request message sent by the client to see what data it > + * provided and what it wants to have. The information is gathered in the > + * "requested_infos" struct. Note that client_id (if provided) points into > + * the odata region, thus the caller must keep odata valid as long as it > + * needs to access the requested_infos struct. > + */ > +static int dhcpv6_parse_info_request(uint8_t *odata, int olen, > + struct requested_infos *ri) > +{ > + int i, req_opt; > + > + while (olen > 4) { > + /* Parse one option */ > + int option = odata[0] << 8 | odata[1]; > + int len = odata[2] << 8 | odata[3]; > + > + if (len + 4 > olen) { > + qemu_log_mask(LOG_GUEST_ERROR, "Guest sent bad DHCPv6 > packet!\n"); > + return -E2BIG; > + } > + > + switch (option) { > + case OPTION_IAADDR: > + /* According to RFC3315, we must discard requests with IA option > */ > + return -EINVAL; > + case OPTION_CLIENTID: > + if (len > 256) { > + /* Avoid very long IDs which could cause problems later */ > + return -E2BIG; > + } > + ri->client_id = odata + 4; > + ri->client_id_len = len; > + break; > + case OPTION_ORO: /* Option request option */ > + if (len & 1) { > + return -EINVAL; > + } > + /* Check which options the client wants to have */ > + for (i = 0; i < len; i += 2) { > + req_opt = odata[4 + i] << 8 | odata[4 + i + 1]; > + switch (req_opt) { > + case OPTION_DNS_SERVERS: > + ri->want_dns = true; > + break; > + case OPTION_BOOTFILE_URL: > + ri->want_boot_url = true; > + break; > + default: > + DEBUG_MISC((dfd, "dhcpv6: Unsupported option request > %d\n", > + req_opt)); > + } > + } > + break; > + default: > + DEBUG_MISC((dfd, "dhcpv6 info req: Unsupported option %d, > len=%d\n", > + option, len)); > + } > + > + odata += len + 4; > + olen -= len + 4; > + } > + > + return 0; > +} > + > + > +/** > + * Handle information request messages > + */ > +static void dhcpv6_info_request(Slirp *slirp, struct sockaddr_in6 *srcsas, > + uint32_t xid, uint8_t *odata, int olen) > +{ > + struct requested_infos ri = { NULL }; > + struct sockaddr_in6 sa6, da6; > + struct mbuf *m; > + uint8_t *resp; > + > + if (dhcpv6_parse_info_request(odata, olen, &ri) < 0) { > + return; > + } > + > + m = m_get(slirp); > + if (!m) { > + return; > + } > + memset(m->m_data, 0, m->m_size); > + m->m_data += IF_MAXLINKHDR; > + resp = (uint8_t *)m->m_data + sizeof(struct ip6) + sizeof(struct udphdr); > + > + /* Fill in response */ > + *resp++ = MSGTYPE_REPLY; > + *resp++ = (uint8_t)(xid >> 16); > + *resp++ = (uint8_t)(xid >> 8); > + *resp++ = (uint8_t)xid; > + > + if (ri.client_id) { > + *resp++ = OPTION_CLIENTID >> 8; /* option-code high byte */ > + *resp++ = OPTION_CLIENTID; /* option-code low byte */ > + *resp++ = ri.client_id_len >> 8; /* option-len high byte */ > + *resp++ = ri.client_id_len; /* option-len low byte */ > + memcpy(resp, ri.client_id, ri.client_id_len); > + resp += ri.client_id_len; > + } > + if (ri.want_dns) { > + *resp++ = OPTION_DNS_SERVERS >> 8; /* option-code high byte */ > + *resp++ = OPTION_DNS_SERVERS; /* option-code low byte */ > + *resp++ = 0; /* option-len high byte */ > + *resp++ = 16; /* option-len low byte */ > + memcpy(resp, &slirp->vnameserver_addr6, 16); > + resp += 16; > + } > + if (ri.want_boot_url) { > + uint8_t *sa = slirp->vhost_addr6.s6_addr; > + int slen, smaxlen; > + > + *resp++ = OPTION_BOOTFILE_URL >> 8; /* option-code high byte */ > + *resp++ = OPTION_BOOTFILE_URL; /* option-code low byte */ > + smaxlen = (uint8_t *)m->m_data + IF_MTU - (resp + 2); > + slen = snprintf((char *)resp + 2, smaxlen, > + "tftp://[%02x%02x:%02x%02x:%02x%02x:%02x%02x:" > + "%02x%02x:%02x%02x:%02x%02x:%02x%02x]/%s", > + sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], sa[6], > sa[7], > + sa[8], sa[9], sa[10], sa[11], sa[12], sa[13], sa[14], > + sa[15], slirp->bootp_filename); > + slen = min(slen, smaxlen); > + *resp++ = slen >> 8; /* option-len high byte */ > + *resp++ = slen; /* option-len low byte */ > + resp += slen; > + } > + > + sa6.sin6_addr = slirp->vhost_addr6; > + sa6.sin6_port = DHCPV6_SERVER_PORT; > + da6.sin6_addr = srcsas->sin6_addr; > + da6.sin6_port = srcsas->sin6_port; > + m->m_data += sizeof(struct ip6) + sizeof(struct udphdr); > + m->m_len = resp - (uint8_t *)m->m_data; > + udp6_output(NULL, m, &sa6, &da6); > +} > + > +/** > + * Handle DHCPv6 messages sent by the client > + */ > +void dhcpv6_input(struct sockaddr_in6 *srcsas, struct mbuf *m) > +{ > + uint8_t *data = (uint8_t *)m->m_data + sizeof(struct udphdr); > + int data_len = m->m_len - sizeof(struct udphdr); > + uint32_t xid; > + > + if (data_len < 4) { > + return; > + } > + > + xid = ntohl(*(uint32_t *)data) & 0xffffff; > + > + switch (data[0]) { > + case MSGTYPE_INFO_REQUEST: > + dhcpv6_info_request(m->slirp, srcsas, xid, &data[4], data_len - 4); > + break; > + default: > + DEBUG_MISC((dfd, "dhcpv6_input: Unsupported message type 0x%x\n", > + data[0])); > + } > +} > diff --git a/slirp/dhcpv6.h b/slirp/dhcpv6.h > new file mode 100644 > index 0000000..9189cd3 > --- /dev/null > +++ b/slirp/dhcpv6.h > @@ -0,0 +1,22 @@ > +/* > + * Definitions and prototypes for SLIRP stateless DHCPv6 > + * > + * Copyright 2016 Thomas Huth, Red Hat Inc. > + * > + * This work is licensed under the terms of the GNU GPL, version 2 > + * or later. See the COPYING file in the top-level directory. > + */ > +#ifndef SLIRP_DHCPV6_H > +#define SLIRP_DHCPV6_H > + > +#define DHCPV6_SERVER_PORT 547 > + > +#define ALLDHCP_MULTICAST { .s6_addr = \ > + { 0xff, 0x02, 0x00, 0x00,\ > + 0x00, 0x00, 0x00, 0x00,\ > + 0x00, 0x00, 0x00, 0x00,\ > + 0x00, 0x01, 0x00, 0x02 } } > + > +void dhcpv6_input(struct sockaddr_in6 *srcsas, struct mbuf *m); > + > +#endif > diff --git a/slirp/udp6.c b/slirp/udp6.c > index 94efb13..9fa314b 100644 > --- a/slirp/udp6.c > +++ b/slirp/udp6.c > @@ -7,6 +7,7 @@ > #include "qemu-common.h" > #include "slirp.h" > #include "udp.h" > +#include "dhcpv6.h" > > void udp6_input(struct mbuf *m) > { > @@ -61,7 +62,17 @@ void udp6_input(struct mbuf *m) > lhost.sin6_addr = ip->ip_src; > lhost.sin6_port = uh->uh_sport; > > - /* TODO handle DHCP/BOOTP */ > + /* handle DHCPv6 */ > + if (ntohs(uh->uh_dport) == DHCPV6_SERVER_PORT && > + (in6_equal(&ip->ip_dst, &slirp->vhost_addr6) || > + in6_equal(&ip->ip_dst, &(struct in6_addr)ALLDHCP_MULTICAST))) { > + m->m_data += iphlen; > + m->m_len -= iphlen; > + dhcpv6_input(&lhost, m); > + m->m_data -= iphlen; > + m->m_len += iphlen; > + goto bad; > + } > > /* handle TFTP */ > if (ntohs(uh->uh_dport) == TFTP_SERVER && > -- > 1.8.3.1 > -- Samuel /* * [...] Note that 120 sec is defined in the protocol as the maximum * possible RTT. I guess we'll have to use something other than TCP * to talk to the University of Mars. * PAWS allows us longer timeouts and large windows, so once implemented * ftp to mars will work nicely. */ (from /usr/src/linux/net/inet/tcp.c, concerning RTT [retransmission timeout])