diff options
author | Rob Gulewich <robert.gulewich@joyent.com> | 2011-08-11 01:17:11 +0000 |
---|---|---|
committer | Rob Gulewich <robert.gulewich@joyent.com> | 2011-08-11 01:17:11 +0000 |
commit | c3b74f8afd4e7fe1cfc0704b148a9e7a48ffaf36 (patch) | |
tree | b36888d3c5ca30e70b876d3aa5e90b1bfe857027 | |
parent | 713c32b302bc536ff68d9f886f3cc43f5bd6b40d (diff) | |
download | illumos-kvm-cmd-c3b74f8afd4e7fe1cfc0704b148a9e7a48ffaf36.tar.gz |
[HVM-484] Add support for DHCP responses from Qemu in SmartOS version of Qemu
-rw-r--r-- | Makefile.objs | 1 | ||||
-rw-r--r-- | net.c | 37 | ||||
-rw-r--r-- | net/vnic-dhcp.c | 703 | ||||
-rw-r--r-- | net/vnic-dhcp.h | 53 | ||||
-rw-r--r-- | net/vnic.c | 29 |
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) @@ -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 */ @@ -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 |