summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Gulewich <robert.gulewich@joyent.com>2011-08-11 01:17:11 +0000
committerRob Gulewich <robert.gulewich@joyent.com>2011-08-11 01:17:11 +0000
commitc3b74f8afd4e7fe1cfc0704b148a9e7a48ffaf36 (patch)
treeb36888d3c5ca30e70b876d3aa5e90b1bfe857027
parent713c32b302bc536ff68d9f886f3cc43f5bd6b40d (diff)
downloadillumos-kvm-cmd-c3b74f8afd4e7fe1cfc0704b148a9e7a48ffaf36.tar.gz
[HVM-484] Add support for DHCP responses from Qemu in SmartOS version of Qemu
-rw-r--r--Makefile.objs1
-rw-r--r--net.c37
-rw-r--r--net/vnic-dhcp.c703
-rw-r--r--net/vnic-dhcp.h53
-rw-r--r--net/vnic.c29
5 files changed, 821 insertions, 2 deletions
diff --git a/Makefile.objs b/Makefile.objs
index 7b85e9e..db43f72 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -45,6 +45,7 @@ net-nested-$(CONFIG_HAIKU) += tap-haiku.o
net-nested-$(CONFIG_SLIRP) += slirp.o
net-nested-$(CONFIG_VDE) += vde.o
net-nested-$(CONFIG_SUNOS_VNIC) += vnic.o
+net-nested-$(CONFIG_SUNOS_VNIC) += vnic-dhcp.o
net-obj-y += $(addprefix net/, $(net-nested-y))
ifeq ($(CONFIG_VIRTIO)$(CONFIG_VIRTFS),yy)
diff --git a/net.c b/net.c
index 73ee212..b55cd44 100644
--- a/net.c
+++ b/net.c
@@ -1102,11 +1102,46 @@ static const struct {
.type = QEMU_OPT_STRING,
.help = "vnic interface name",
},
- {
+ {
.name = "macaddr",
.type = QEMU_OPT_STRING,
.help = "MAC address",
},
+ {
+ .name = "ip",
+ .type = QEMU_OPT_STRING,
+ .help = "DHCP IP address",
+ },
+ {
+ .name = "netmask",
+ .type = QEMU_OPT_STRING,
+ .help = "DHCP netmask",
+ },
+ {
+ .name = "gateway_ip",
+ .type = QEMU_OPT_STRING,
+ .help = "DHCP gateway IP address",
+ },
+ {
+ .name = "server_ip",
+ .type = QEMU_OPT_STRING,
+ .help = "IP address to return as the DHCP server",
+ },
+ {
+ .name = "dns_ip",
+ .type = QEMU_OPT_STRING,
+ .help = "DHCP DNS server IP address",
+ },
+ {
+ .name = "hostname",
+ .type = QEMU_OPT_STRING,
+ .help = "DHCP DNS server IP address",
+ },
+ {
+ .name = "lease_time",
+ .type = QEMU_OPT_NUMBER,
+ .help = "DHCP DNS server lease time",
+ },
{ /* end of list */ }
},
#endif
diff --git a/net/vnic-dhcp.c b/net/vnic-dhcp.c
new file mode 100644
index 0000000..5a7be69
--- /dev/null
+++ b/net/vnic-dhcp.c
@@ -0,0 +1,703 @@
+/*
+ * QEMU System Emulator
+ * Solaris VNIC DHCP support
+ *
+ * Copyright (c) 2011 Joyent, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <arpa/inet.h>
+
+#include "net/vnic-dhcp.h"
+
+#include <slirp.h> /* GodDAMN */
+#include "ip.h"
+#include "udp.h"
+#include "qemu-error.h"
+
+/* from slirp.c: */
+#define ETH_ALEN 6
+#define ETH_HLEN 14
+#define ETH_P_IP 0x0800 /* Internet Protocol packet */
+#define ETH_P_ARP 0x0806 /* Address Resolution packet */
+/* from bootp.c: */
+#define LEASE_TIME (24 * 3600)
+/* from slirp/debug.h: */
+#define dfd stderr
+/* from bootp.c and modified: */
+#ifdef VNIC_DHCP_DEBUG
+#define DPRINTF(fmt, ...) \
+do if (VNIC_DHCP_DEBUG) { fprintf(dfd, fmt, ## __VA_ARGS__); fflush(dfd); } \
+ while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+/* from cksum.c: */
+#define ADDCARRY(x) (x > 65535 ? x -= 65535 : x)
+#define REDUCE {l_util.l = sum; sum = l_util.s[0] + l_util.s[1]; \
+ (void)ADDCARRY(sum);}
+
+/* from slirp.c: */
+struct ethhdr
+{
+ unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
+ unsigned char h_source[ETH_ALEN]; /* source ether addr */
+ unsigned short h_proto; /* packet type ID field */
+};
+
+/* from bootp.c: */
+static const uint8_t rfc1533_cookie[] = { RFC1533_COOKIE };
+/* emulated hosts use the MAC addr 52:55:IP:IP:IP:IP */
+static const uint8_t special_ethaddr[6] = {
+ 0x52, 0x55, 0x55, 0x55, 0x55, 0x55
+};
+
+static void
+print_dhcp_info(const struct bootp_t *bp)
+{
+ char ip_str[INET_ADDRSTRLEN];
+ char *macaddr;
+ DPRINTF(" bp->bp_op=%d\n", bp->bp_op);
+
+ inet_ntop(AF_INET, &(bp->ip.ip_src), ip_str, INET_ADDRSTRLEN);
+ DPRINTF(" bp->ip_src=%s\n", ip_str);
+ inet_ntop(AF_INET, &(bp->ip.ip_dst), ip_str, INET_ADDRSTRLEN);
+ DPRINTF(" bp->ip_dst=%s\n", ip_str);
+ DPRINTF(" bp->udp.uh_sport=%d\n", ntohs(bp->udp.uh_sport));
+ DPRINTF(" bp->udp.uh_dport=%d\n", ntohs(bp->udp.uh_dport));
+
+ macaddr = _link_ntoa(bp->bp_hwaddr, ip_str, ETH_ALEN, 0);
+ DPRINTF(" bp->bp_hwaddr=%s\n", macaddr);
+ free(macaddr);
+
+ inet_ntop(AF_INET, &(bp->bp_yiaddr), ip_str, INET_ADDRSTRLEN);
+ DPRINTF(" bp->bp_yiaddr=%s\n", ip_str);
+ inet_ntop(AF_INET, &(bp->bp_siaddr), ip_str, INET_ADDRSTRLEN);
+ DPRINTF(" bp->bp_siaddr=%s\n", ip_str);
+ inet_ntop(AF_INET, &(bp->bp_giaddr), ip_str, INET_ADDRSTRLEN);
+ DPRINTF(" bp->bp_giaddr=%s\n", ip_str);
+ inet_ntop(AF_INET, &(bp->bp_ciaddr), ip_str, INET_ADDRSTRLEN);
+ DPRINTF(" bp->bp_ciaddr=%s\n", ip_str);
+}
+
+/* from bootp.c: */
+static void
+dhcp_decode(const struct bootp_t *bp, int *pmsg_type,
+ struct in_addr *preq_addr)
+{
+ const uint8_t *p, *p_end;
+ int len, tag;
+
+ *pmsg_type = 0;
+ preq_addr->s_addr = htonl(0L);
+
+ p = bp->bp_vend;
+ p_end = p + DHCP_OPT_LEN;
+ if (memcmp(p, rfc1533_cookie, 4) != 0)
+ return;
+ p += 4;
+ while (p < p_end) {
+ tag = p[0];
+ if (tag == RFC1533_PAD) {
+ p++;
+ } else if (tag == RFC1533_END) {
+ break;
+ } else {
+ p++;
+ if (p >= p_end)
+ break;
+ len = *p++;
+ DPRINTF("dhcp: tag=%d len=%d\n", tag, len);
+
+ switch(tag) {
+ case RFC2132_MSG_TYPE:
+ if (len >= 1)
+ *pmsg_type = p[0];
+ break;
+ case RFC2132_REQ_ADDR:
+ if (len >= 4) {
+ memcpy(&(preq_addr->s_addr), p, 4);
+ }
+ break;
+ default:
+ break;
+ }
+ p += len;
+ }
+ }
+ if (*pmsg_type == DHCPREQUEST && preq_addr->s_addr == htonl(0L) &&
+ bp->bp_ciaddr.s_addr) {
+ memcpy(&(preq_addr->s_addr), &bp->bp_ciaddr, 4);
+ }
+}
+
+#if VNIC_DHCP_HEX_DUMP
+/* from net.c: */
+static void
+hex_dump(FILE *f, const uint8_t *buf, int size)
+{
+ int len, i, j, c;
+
+ for(i = 0; i < size; i += 16) {
+ len = size - i;
+ if (len > 16)
+ len = 16;
+ fprintf(f, "%08x ", i);
+ for(j = 0; j < 16; j++) {
+ if (j < len)
+ fprintf(f, " %02x", buf[i + j]);
+ else
+ fprintf(f, " ");
+ }
+ fprintf(f, " ");
+ for(j = 0; j < len; j++) {
+ c = buf[i + j];
+ if (c < ' ' || c > '~')
+ c = '.';
+ fprintf(f, "%c", c);
+ }
+ fprintf(f, "\n");
+ }
+}
+#endif
+
+
+/* from cksum.c, and modified to work on non-mbufs: */
+static int
+cksum2(uint16_t *m, int len)
+{
+ register uint16_t *w = m;
+ register int sum = 0;
+ register int mlen = len;
+ int byte_swapped = 0;
+
+ union {
+ uint8_t c[2];
+ uint16_t s;
+ } s_util;
+ union {
+ uint16_t s[2];
+ uint32_t l;
+ } l_util;
+
+#ifdef DEBUG
+ len -= mlen;
+#endif
+ /*
+ * Force to even boundary.
+ */
+ if ((1 & (long) w) && (mlen > 0)) {
+ REDUCE;
+ sum <<= 8;
+ s_util.c[0] = *(uint8_t *)w;
+ w = (uint16_t *)((int8_t *)w + 1);
+ mlen--;
+ byte_swapped = 1;
+ }
+ /*
+ * Unroll the loop to make overhead from
+ * branches &c small.
+ */
+ while ((mlen -= 32) >= 0) {
+ sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
+ sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7];
+ sum += w[8]; sum += w[9]; sum += w[10]; sum += w[11];
+ sum += w[12]; sum += w[13]; sum += w[14]; sum += w[15];
+ w += 16;
+ }
+ mlen += 32;
+ while ((mlen -= 8) >= 0) {
+ sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
+ w += 4;
+ }
+ mlen += 8;
+ if (mlen == 0 && byte_swapped == 0)
+ goto cont;
+ REDUCE;
+ while ((mlen -= 2) >= 0) {
+ sum += *w++;
+ }
+
+ if (byte_swapped) {
+ REDUCE;
+ sum <<= 8;
+ if (mlen == -1) {
+ s_util.c[1] = *(uint8_t *)w;
+ sum += s_util.s;
+ mlen = 0;
+ } else
+
+ mlen = -1;
+ } else if (mlen == -1)
+ s_util.c[0] = *(uint8_t *)w;
+
+cont:
+#ifdef DEBUG
+ if (len) {
+ DPRINTF("cksum: out of data\n");
+ DPRINTF(" len = %d\n", len);
+ }
+#endif
+ if (mlen == -1) {
+ /* The last mbuf has odd # of bytes. Follow the
+ standard (the odd byte may be shifted left by 8 bits
+ or not as determined by endian-ness of the machine) */
+ s_util.c[1] = 0;
+ sum += s_util.s;
+ }
+ REDUCE;
+ return (~sum & 0xffff);
+}
+
+#ifdef VNIC_DHCP_DEBUG
+void
+debug_eth_frame(const uint8_t *buf_p, size_t size)
+{
+ int proto;
+ char ip_str[INET_ADDRSTRLEN];
+ struct ip *ip;
+ char *macaddr;
+
+ DPRINTF("ethernet frame: ");
+#if VNIC_DHCP_HEX_DUMP
+ hex_dump(dfd, buf_p, size);
+#endif
+
+ if (size < ETH_HLEN) {
+ DPRINTF("size %d < ETH_HLEN\n", (int)size);
+ }
+
+ macaddr = _link_ntoa(((struct ethhdr *)buf_p)->h_source, ip_str,
+ ETH_ALEN, 0);
+ DPRINTF(" src mac=%s", macaddr);
+ free(macaddr);
+
+ macaddr = _link_ntoa(((struct ethhdr *)buf_p)->h_dest, ip_str,
+ ETH_ALEN, 0);
+ DPRINTF(" dst mac=%s", macaddr);
+ free(macaddr);
+
+ proto = ntohs(*(uint16_t *)(buf_p + 12));
+ DPRINTF(" proto=%d ", proto);
+ /* XXX: does this work with VLAN tags? */
+
+ switch (proto) {
+ case ETH_P_ARP:
+ DPRINTF("(ETH_P_ARP)\n");
+ break;
+ case ETH_P_IP:
+ DPRINTF("(ETH_P_IP)\n");
+ break;
+ default:
+ DPRINTF("(unknown)\n");
+ break;
+ }
+
+ if (size < sizeof(struct ip)) {
+ DPRINTF(" (len < sizeof(struct ip))\n");
+ return;
+ }
+
+ ip = (struct ip *)(buf_p + ETH_HLEN);
+ DPRINTF(" ip version: %d\n", ip->ip_v);
+ if (ip->ip_v != IPVERSION)
+ return;
+
+ inet_ntop(AF_INET, &(ip->ip_src), ip_str, INET_ADDRSTRLEN);
+ DPRINTF(" ip_src=%s\n", ip_str);
+ inet_ntop(AF_INET, &(ip->ip_dst), ip_str, INET_ADDRSTRLEN);
+ DPRINTF(" ip_dst=%s\n", ip_str);
+
+ DPRINTF(" ip_proto=");
+ switch (ip->ip_p) {
+ case IPPROTO_TCP:
+ DPRINTF("IPPROTO_TCP\n");
+ break;
+ case IPPROTO_UDP:
+ DPRINTF("IPPROTO_UDP\n");
+ break;
+ case IPPROTO_ICMP:
+ DPRINTF("IPPROTO_ICMP\n");
+ break;
+ default:
+ DPRINTF("unknown protocol %d\n", ip->ip_p);
+ }
+
+ if (ip->ip_dst.s_addr == 0xffffffff && ip->ip_p == IPPROTO_UDP) {
+ DPRINTF(" UDP broadcast\n");
+ }
+
+ if (ip->ip_p == IPPROTO_UDP) {
+ struct udphdr *uh = (struct udphdr *)(buf_p + ETH_HLEN +
+ (ip->ip_hl << 2));
+ DPRINTF(" uh_sport=%d\n", ntohs(uh->uh_sport));
+ DPRINTF(" uh_dport=%d\n", ntohs(uh->uh_dport));
+ DPRINTF(" uh_ulen=%d\n", uh->uh_ulen);
+
+ if (ntohs(uh->uh_dport) == BOOTP_SERVER) {
+ struct bootp_t bp;
+ DPRINTF(" BOOTP_SERVER\n");
+ memcpy(&bp, ip, sizeof(struct ip));
+ memcpy(&(bp.udp), uh, sizeof(struct bootp_t) -
+ sizeof(struct ip));
+ print_dhcp_info(&bp);
+ }
+
+ if (ntohs(uh->uh_dport) == TFTP_SERVER) {
+ DPRINTF(" TFTP_SERVER\n");
+ }
+ }
+}
+#endif
+
+static int
+populate_dhcp_reply(const struct bootp_t *bp, struct bootp_t *rbp,
+ struct sockaddr_in * saddr, struct sockaddr_in *daddr, VNICDHCPState *vdsp)
+{
+ uint8_t *q;
+ struct in_addr preq_addr;
+ int dhcp_msg_type, val;
+
+ /* extract exact DHCP msg type */
+ dhcp_decode(bp, &dhcp_msg_type, &preq_addr);
+ DPRINTF("bootp packet op=%d msgtype=%d", bp->bp_op, dhcp_msg_type);
+ if (preq_addr.s_addr != htonl(0L))
+ DPRINTF(" req_addr=%08x\n", ntohl(preq_addr.s_addr));
+ else
+ DPRINTF("\n");
+
+ if (dhcp_msg_type == 0)
+ dhcp_msg_type = DHCPREQUEST; /* Force reply for old clients */
+
+ if (dhcp_msg_type != DHCPDISCOVER &&
+ dhcp_msg_type != DHCPREQUEST)
+ return (0);
+
+ memset(rbp, 0, sizeof(struct bootp_t));
+
+ rbp->bp_op = BOOTP_REPLY;
+ rbp->bp_xid = bp->bp_xid;
+ rbp->bp_htype = 1;
+ rbp->bp_hlen = 6;
+ memcpy(rbp->bp_hwaddr, bp->bp_hwaddr, 6);
+ rbp->bp_yiaddr = daddr->sin_addr;
+ rbp->bp_siaddr = saddr->sin_addr;
+ q = rbp->bp_vend;
+ memcpy(q, rfc1533_cookie, 4);
+ q += 4;
+
+ if (dhcp_msg_type == DHCPDISCOVER || dhcp_msg_type == DHCPREQUEST) {
+ DPRINTF("%s addr=%08x\n",
+ (dhcp_msg_type == DHCPDISCOVER) ? "offered" : "ack'ed",
+ ntohl(daddr->sin_addr.s_addr));
+
+ *q++ = RFC2132_MSG_TYPE;
+ *q++ = 1;
+ /* DHCPREQUEST */
+ *q++ = (dhcp_msg_type == DHCPDISCOVER) ? DHCPOFFER : DHCPACK;
+
+ *q++ = RFC2132_SRV_ID;
+ *q++ = 4;
+ memcpy(q, &saddr->sin_addr, 4);
+ q += 4;
+
+ // netmask
+ *q++ = RFC1533_NETMASK;
+ *q++ = 4;
+ memcpy(q, &vdsp->vnds_netmask_addr, sizeof(struct in_addr));
+ q += 4;
+
+ // default gw
+ *q++ = RFC1533_GATEWAY;
+ *q++ = 4;
+ memcpy(q, &vdsp->vnds_gw_addr, sizeof(struct in_addr));
+ q += 4;
+
+ // dns server list
+ *q++ = RFC1533_DNS;
+ *q++ = 4;
+ memcpy(q, &vdsp->vnds_dns_addr, sizeof(struct in_addr));
+ q += 4;
+
+ // lease time
+ *q++ = RFC2132_LEASE_TIME;
+ *q++ = 4;
+ memcpy(q, &vdsp->vnds_lease_time, 4);
+ q += 4;
+
+ // hostname
+ val = strlen(vdsp->vnds_client_hostname);
+ *q++ = RFC1533_HOSTNAME;
+ *q++ = val;
+ memcpy(q, &vdsp->vnds_client_hostname, val);
+ q += val;
+ } else {
+ static const char nak_msg[] = "requested address not available";
+
+ DPRINTF("nak'ed addr=%08x\n", ntohl(preq_addr.s_addr));
+
+ *q++ = RFC2132_MSG_TYPE;
+ *q++ = 1;
+ *q++ = DHCPNAK;
+
+ *q++ = RFC2132_MESSAGE;
+ *q++ = sizeof(nak_msg) - 1;
+ memcpy(q, nak_msg, sizeof(nak_msg) - 1);
+ q += sizeof(nak_msg) - 1;
+ }
+ *q = RFC1533_END;
+
+ return (1);
+}
+
+static void
+add_udpip_header(struct udpiphdr *ui, struct sockaddr_in *saddr,
+ struct sockaddr_in *daddr)
+{
+ memset(&ui->ui_i.ih_mbuf, 0 , sizeof(struct mbuf_ptr));
+ ui->ui_x1 = 0;
+ ui->ui_pr = IPPROTO_UDP;
+ ui->ui_len = htons(sizeof(struct bootp_t) - sizeof(struct ip));
+ ui->ui_src = saddr->sin_addr;
+ ui->ui_dst = daddr->sin_addr;
+ ui->ui_sport = saddr->sin_port;
+ ui->ui_dport = daddr->sin_port;
+ ui->ui_ulen = htons(sizeof(struct bootp_t) - sizeof(struct ip));
+
+ ui->ui_sum = 0;
+ if ((ui->ui_sum = cksum2((uint16_t *)ui, sizeof(struct bootp_t))) == 0)
+ ui->ui_sum = 0xffff;
+}
+
+static void
+add_ip_header(struct ip *ip, VNICDHCPState *vdsp)
+{
+ register int ip_hlen = sizeof(struct ip);
+ register int ip_len = sizeof(struct bootp_t);
+
+ ip->ip_v = IPVERSION;
+ ip->ip_off &= IP_DF;
+ ip->ip_id = htons(vdsp->vnds_ip_id++);
+ ip->ip_hl = ip_hlen >> 2;
+
+ ip->ip_ttl = IPDEFTTL;
+ ip->ip_tos = IPTOS_LOWDELAY;
+
+ ip->ip_len = htons((uint16_t)ip_len);
+ ip->ip_off = htons((uint16_t)ip->ip_off);
+ ip->ip_sum = 0;
+ ip->ip_sum = cksum2((uint16_t *) ip, ip_hlen);
+}
+
+static int
+dhcp_reply(const struct bootp_t *bp, const unsigned char *src_mac,
+ VNICDHCPState *vdsp)
+{
+ struct sockaddr_in saddr, daddr;
+ struct ethhdr *eh;
+ struct bootp_t *rbp = (struct bootp_t *)(vdsp->vnds_buf + ETH_HLEN);
+
+ /* Client IP address */
+ memcpy(&daddr.sin_addr, &vdsp->vnds_client_addr,
+ sizeof(struct in_addr));
+ daddr.sin_port = htons(BOOTP_CLIENT);
+
+ /* Server IP address */
+ memcpy(&saddr.sin_addr, &vdsp->vnds_srv_addr, sizeof(struct in_addr));
+ saddr.sin_port = htons(BOOTP_SERVER);
+
+ /* Now set all of the DHCP options */
+ if (!populate_dhcp_reply(bp, rbp, &saddr, &daddr, vdsp))
+ return (0);
+
+ daddr.sin_addr.s_addr = 0xffffffffu;
+
+ /* Buffer Layout:
+ *
+ * | ethhdr | bootp_t |
+ * | ip | udphdr | dhcp payload |
+ * | udpiphdr |
+ *
+ */
+
+ /* Ethernet header */
+ eh = (struct ethhdr *)vdsp->vnds_buf;
+ memcpy(eh->h_dest, src_mac, ETH_ALEN);
+ memcpy(eh->h_source, special_ethaddr, ETH_ALEN);
+ eh->h_proto = htons(ETH_P_IP);
+
+ /* IP pseudo header (for UDP checksum) */
+ add_udpip_header((struct udpiphdr *)rbp, &saddr, &daddr);
+
+ add_ip_header((struct ip *)rbp, vdsp);
+
+#if VNIC_DHCP_DEBUG
+ DPRINTF("= dhcp reply =\n");
+ debug_eth_frame((const uint8_t *)vdsp->vnds_buf,
+ sizeof(struct bootp_t) + sizeof(struct ethhdr));
+ DPRINTF("= dhcp reply end =\n");
+#endif
+
+ return (sizeof(struct bootp_t) + sizeof(struct ethhdr));
+}
+
+int
+create_dhcp_response(const uint8_t *buf_p, int pkt_len, VNICDHCPState *vdsp)
+{
+ struct bootp_t bp;
+ register struct udphdr *uh;
+ register struct ip *ip = (struct ip *)(buf_p + ETH_HLEN);
+
+ DPRINTF("create_dhcp_response()\n");
+
+ if (ip->ip_p != IPPROTO_UDP) {
+ return (0);
+ }
+
+ uh = (struct udphdr *) (buf_p + ETH_HLEN + (ip->ip_hl << 2));
+ if (ntohs(uh->uh_dport) != BOOTP_SERVER) {
+ return (0);
+ }
+
+ /* Trim out IP options (if any) */
+ memcpy(&bp, ip, sizeof(struct ip));
+ memcpy(&(bp.udp), uh, sizeof(struct bootp_t) - sizeof(struct ip));
+
+ if (bp.bp_op != BOOTP_REQUEST) {
+ return (0);
+ }
+
+ return (dhcp_reply(&bp, ((struct ethhdr *)buf_p)->h_dest, vdsp));
+}
+
+int
+is_dhcp_request(const uint8_t *buf_p, size_t size)
+{
+ struct ip *ip;
+ struct udphdr *uh;
+
+ DPRINTF("is_dhcp_request(): ");
+ if (size < ETH_HLEN || (ntohs(*(uint16_t *)(buf_p + 12)) != ETH_P_IP) ||
+ size < sizeof (struct ip)) {
+ DPRINTF("packet too small\n");
+ return (0);
+ }
+
+ ip = (struct ip *)(buf_p + ETH_HLEN);
+
+ if (ip->ip_v != IPVERSION) {
+ DPRINTF("not an IPv4 packet\n");
+ return (0);
+ }
+
+ if (ip->ip_p != IPPROTO_UDP) {
+ DPRINTF("not a UDP packet\n");
+ return (0);
+ }
+
+ uh = (struct udphdr *)(buf_p + ETH_HLEN + (ip->ip_hl << 2));
+ if (ntohs(uh->uh_dport) == BOOTP_SERVER) {
+ DPRINTF("is a DHCP request\n");
+ return (1);
+ }
+
+ DPRINTF("UDP packet, but not a DHCP request\n");
+ return (0);
+}
+
+static int
+qemu_ip_opt(QemuOpts *opts, const char *opt_name, struct in_addr *addr)
+{
+ const char *opt;
+ if ((opt = qemu_opt_get(opts, opt_name)) == NULL) {
+ error_report("missing %s for vnic dhcp\n", opt_name);
+ return (0);
+ }
+
+ if (!inet_pton(AF_INET, opt, addr)) {
+ error_report("invalid %s '%s' for vnic dhcp\n", opt_name, opt);
+ return (-1);
+ }
+
+ return (1);
+}
+
+int
+vnic_dhcp_init(VNICDHCPState *vdsp, QemuOpts *opts)
+{
+ int ret;
+ uint32_t lease_time;
+ const char *hostname;
+
+ /* Use the ip option to determine if dhcp should be enabled */
+ if (qemu_opt_get(opts, "ip") == NULL) {
+ error_report("vnic dhcp disabled\n");
+ vdsp->vnds_enabled = 0;
+ return (1);
+ }
+
+ if (!qemu_ip_opt(opts, "ip", &(vdsp->vnds_client_addr)))
+ return (0);
+
+ if (!qemu_ip_opt(opts, "netmask", &(vdsp->vnds_netmask_addr)))
+ return (0);
+
+ if (!(ret = qemu_ip_opt(opts, "server_ip", &(vdsp->vnds_srv_addr)))) {
+ if (ret == 0) {
+ /* default DHCP server address */
+ inet_pton(AF_INET, "169.254.169.254",
+ &(vdsp->vnds_srv_addr));
+ } else {
+ return (0);
+ }
+ }
+
+ if (!qemu_ip_opt(opts, "gateway_ip", &(vdsp->vnds_gw_addr)))
+ return (0);
+
+ if (!(ret = qemu_ip_opt(opts, "dns_ip", &(vdsp->vnds_dns_addr)))) {
+ if (ret == 0) {
+ /* default DNS server */
+ inet_pton(AF_INET, "8.8.8.8", &(vdsp->vnds_dns_addr));
+ } else {
+ return (0);
+ }
+ }
+
+ if ((hostname = qemu_opt_get(opts, "hostname")) != NULL) {
+ ret = strlen(hostname);
+ if (ret > sizeof(vdsp->vnds_client_hostname)) {
+ error_report("hostname is too long\n");
+ return (-1);
+ }
+ memcpy(&vdsp->vnds_client_hostname, hostname, ret);
+ } else {
+ vdsp->vnds_client_hostname[0] = '\0';
+ }
+
+ lease_time = qemu_opt_get_number(opts, "lease_time", LEASE_TIME);
+ vdsp->vnds_lease_time = htonl(lease_time);
+
+ vdsp->vnds_ip_id = 0;
+ vdsp->vnds_enabled = 1;
+
+ return (1);
+}
diff --git a/net/vnic-dhcp.h b/net/vnic-dhcp.h
new file mode 100644
index 0000000..e8d03cb
--- /dev/null
+++ b/net/vnic-dhcp.h
@@ -0,0 +1,53 @@
+/*
+ * QEMU System Emulator
+ * Solaris VNIC DHCP support
+ *
+ * Copyright (c) 2011 Joyent, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef QEMU_NET_VNIC_DHCP_H
+#define QEMU_NET_VNIC_DHCP_H
+
+#include <netinet/in.h>
+#include "qemu-option.h"
+
+#define VNIC_BUFFSIZE 65536
+#define VNIC_DHCP_DEBUG 1
+#define VNIC_DHCP_HEX_DUMP 1
+
+typedef struct VNICDHCPState {
+ unsigned int vnds_enabled;
+ uint8_t vnds_buf[VNIC_BUFFSIZE];
+ uint16_t vnds_ip_id;
+ struct in_addr vnds_srv_addr;
+ struct in_addr vnds_client_addr;
+ struct in_addr vnds_netmask_addr;
+ struct in_addr vnds_gw_addr;
+ struct in_addr vnds_dns_addr;
+ uint32_t vnds_lease_time;
+ char vnds_client_hostname[33];
+} VNICDHCPState;
+
+int create_dhcp_response(const uint8_t *buf_p, int pkt_len, VNICDHCPState *vdsp);
+int is_dhcp_request(const uint8_t *buf_p, size_t size);
+int vnic_dhcp_init(VNICDHCPState *vdsp, QemuOpts *opts);
+void debug_eth_frame(const uint8_t *buf_p, size_t size);
+
+#endif /* QEMU_NET_VNIC_DHCP_H */
diff --git a/net/vnic.c b/net/vnic.c
index b313550..8813de0 100644
--- a/net/vnic.c
+++ b/net/vnic.c
@@ -39,6 +39,7 @@
#include <fcntl.h>
#include "net/vnic.h"
+#include "net/vnic-dhcp.h"
#include "qemu-common.h"
#include "qemu-error.h"
@@ -59,6 +60,7 @@ typedef struct VNICState {
uint8_t vns_buf[VNIC_BUFFSIZE];
uint_t vns_sap;
dlpi_handle_t vns_hdl;
+ VNICDHCPState vns_ds;
} VNICState;
static void vnic_update_fd_handler(VNICState *);
@@ -103,7 +105,7 @@ vnic_read_packet(VNICState *vsp, uint8_t *buf, int len)
vnic_write_poll(vsp, 1);
return (0);
}
-
+
if (ret == -1) {
return (-1);
}
@@ -150,6 +152,7 @@ vnic_send_completed(VLANClientState *nc, ssize_t len)
vnic_read_poll(vsp, 1);
}
+/* outside world -> VM */
static void
vnic_send(void *opaque)
{
@@ -179,11 +182,32 @@ vnic_writable(void *opaque)
qemu_flush_queued_packets(&vsp->vns_nc);
}
+/* VM -> outside world */
static ssize_t
vnic_receive(VLANClientState *ncp, const uint8_t *buf, size_t size)
{
VNICState *vsp = DO_UPCAST(VNICState, vns_nc, ncp);
+#if VNIC_DHCP_DEBUG
+ debug_eth_frame(buf, size);
+#endif
+
+ if (vsp->vns_ds.vnds_enabled && is_dhcp_request(buf, size)) {
+ int ret;
+
+ // XXX: do we need to handle arp requests for the fake IP?
+ ret = create_dhcp_response(buf, size, &vsp->vns_ds);
+ if (!ret)
+ return size;
+
+ ret = qemu_send_packet_async(&vsp->vns_nc,
+ vsp->vns_ds.vnds_buf, ret, vnic_send_completed);
+ if (ret == 0)
+ vnic_read_poll(vsp, 0);
+
+ return size;
+ }
+
return (vnic_write_packet(vsp, buf, size));
}
@@ -309,6 +333,9 @@ net_init_vnic(QemuOpts *opts, Monitor *mon, const char *name, VLANState *vlan)
snprintf(vsp->vns_nc.info_str, sizeof (vsp->vns_nc.info_str), "ifname=%s",
qemu_opt_get(opts, "ifname"));
+ if (vnic_dhcp_init(&vsp->vns_ds, opts) == 0)
+ return (-1);
+
#ifdef CONFIG_SUNOS_VNIC_KVM
net_init_kvm(fd);
#endif