summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Network/slirp/bootp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/Network/slirp/bootp.c')
-rw-r--r--src/VBox/Devices/Network/slirp/bootp.c969
1 files changed, 969 insertions, 0 deletions
diff --git a/src/VBox/Devices/Network/slirp/bootp.c b/src/VBox/Devices/Network/slirp/bootp.c
new file mode 100644
index 00000000..0ad78891
--- /dev/null
+++ b/src/VBox/Devices/Network/slirp/bootp.c
@@ -0,0 +1,969 @@
+/* $Id: bootp.c $ */
+/** @file
+ * NAT - BOOTP/DHCP server emulation.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * 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, in version 3 of the
+ * License.
+ *
+ * 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * QEMU BOOTP/DHCP server
+ *
+ * Copyright (c) 2004 Fabrice Bellard
+ *
+ * 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 <slirp.h>
+#include <libslirp.h>
+#include <iprt/errcore.h>
+
+/** Entry in the table of known DHCP clients. */
+typedef struct
+{
+ uint32_t xid;
+ bool allocated;
+ uint8_t macaddr[ETH_ALEN];
+ struct in_addr addr;
+ int number;
+} BOOTPClient;
+/** Number of DHCP clients supported by NAT. */
+#define NB_ADDR 16
+
+#define bootp_clients ((BOOTPClient *)pData->pbootp_clients)
+
+/* XXX: only DHCP is supported */
+static const uint8_t rfc1533_cookie[4] = { RFC1533_COOKIE };
+
+static void bootp_reply(PNATState pData, struct mbuf *m0, int offReply, uint16_t flags);
+
+
+static uint8_t *dhcp_find_option(uint8_t *vendor, size_t vlen, uint8_t tag, ssize_t checklen)
+{
+ uint8_t *q = vendor;
+ size_t len = vlen;
+
+ q += sizeof(rfc1533_cookie);
+ len -= sizeof(rfc1533_cookie);
+
+ while (len > 0)
+ {
+ uint8_t *optptr = q;
+ uint8_t opt;
+ uint8_t optlen;
+
+ opt = *q++;
+ --len;
+
+ if (opt == RFC1533_END)
+ break;
+
+ if (opt == RFC1533_PAD)
+ continue;
+
+ if (len == 0)
+ break; /* no option length byte */
+
+ optlen = *q++;
+ --len;
+
+ if (len < optlen)
+ break; /* option value truncated */
+
+ if (opt == tag)
+ {
+ if (checklen > 0 && optlen != checklen)
+ break; /* wrong option size */
+
+ return optptr;
+ }
+
+ q += optlen;
+ len -= optlen;
+ }
+
+ return NULL;
+}
+
+static BOOTPClient *bc_alloc_client(PNATState pData)
+{
+ int i;
+ LogFlowFuncEnter();
+ for (i = 0; i < NB_ADDR; i++)
+ {
+ if (!bootp_clients[i].allocated)
+ {
+ BOOTPClient *bc;
+
+ bc = &bootp_clients[i];
+ memset(bc, 0, sizeof(BOOTPClient));
+ bc->allocated = 1;
+ bc->number = i;
+ LogFlowFunc(("LEAVE: bc:%d\n", bc->number));
+ return bc;
+ }
+ }
+ LogFlowFunc(("LEAVE: NULL\n"));
+ return NULL;
+}
+
+static BOOTPClient *get_new_addr(PNATState pData, struct in_addr *paddr)
+{
+ BOOTPClient *bc;
+ LogFlowFuncEnter();
+ bc = bc_alloc_client(pData);
+ if (!bc)
+ return NULL;
+
+ paddr->s_addr = RT_H2N_U32(RT_N2H_U32(pData->special_addr.s_addr) | (bc->number + START_ADDR));
+ bc->addr.s_addr = paddr->s_addr;
+ LogFlowFunc(("LEAVE: paddr:%RTnaipv4, bc:%d\n", paddr->s_addr, bc->number));
+ return bc;
+}
+
+static int release_addr(PNATState pData, struct in_addr *paddr)
+{
+ unsigned i;
+ for (i = 0; i < NB_ADDR; i++)
+ {
+ if (paddr->s_addr == bootp_clients[i].addr.s_addr)
+ {
+ memset(&bootp_clients[i], 0, sizeof(BOOTPClient));
+ return VINF_SUCCESS;
+ }
+ }
+ return VERR_NOT_FOUND;
+}
+
+/*
+ * from RFC 2131 4.3.1
+ * Field DHCPOFFER DHCPACK DHCPNAK
+ * ----- --------- ------- -------
+ * 'op' BOOTREPLY BOOTREPLY BOOTREPLY
+ * 'htype' (From "Assigned Numbers" RFC)
+ * 'hlen' (Hardware address length in octets)
+ * 'hops' 0 0 0
+ * 'xid' 'xid' from client 'xid' from client 'xid' from client
+ * DHCPDISCOVER DHCPREQUEST DHCPREQUEST
+ * message message message
+ * 'secs' 0 0 0
+ * 'ciaddr' 0 'ciaddr' from 0
+ * DHCPREQUEST or 0
+ * 'yiaddr' IP address offered IP address 0
+ * to client assigned to client
+ * 'siaddr' IP address of next IP address of next 0
+ * bootstrap server bootstrap server
+ * 'flags' 'flags' from 'flags' from 'flags' from
+ * client DHCPDISCOVER client DHCPREQUEST client DHCPREQUEST
+ * message message message
+ * 'giaddr' 'giaddr' from 'giaddr' from 'giaddr' from
+ * client DHCPDISCOVER client DHCPREQUEST client DHCPREQUEST
+ * message message message
+ * 'chaddr' 'chaddr' from 'chaddr' from 'chaddr' from
+ * client DHCPDISCOVER client DHCPREQUEST client DHCPREQUEST
+ * message message message
+ * 'sname' Server host name Server host name (unused)
+ * or options or options
+ * 'file' Client boot file Client boot file (unused)
+ * name or options name or options
+ * 'options' options options
+ *
+ * Option DHCPOFFER DHCPACK DHCPNAK
+ * ------ --------- ------- -------
+ * Requested IP address MUST NOT MUST NOT MUST NOT
+ * IP address lease time MUST MUST (DHCPREQUEST) MUST NOT
+ * MUST NOT (DHCPINFORM)
+ * Use 'file'/'sname' fields MAY MAY MUST NOT
+ * DHCP message type DHCPOFFER DHCPACK DHCPNAK
+ * Parameter request list MUST NOT MUST NOT MUST NOT
+ * Message SHOULD SHOULD SHOULD
+ * Client identifier MUST NOT MUST NOT MAY
+ * Vendor class identifier MAY MAY MAY
+ * Server identifier MUST MUST MUST
+ * Maximum message size MUST NOT MUST NOT MUST NOT
+ * All others MAY MAY MUST NOT
+ */
+static BOOTPClient *find_addr(PNATState pData, struct in_addr *paddr, const uint8_t *macaddr)
+{
+ int i;
+
+ LogFlowFunc(("macaddr:%RTmac\n", macaddr));
+ for (i = 0; i < NB_ADDR; i++)
+ {
+ if ( memcmp(macaddr, bootp_clients[i].macaddr, ETH_ALEN) == 0
+ && bootp_clients[i].allocated != 0)
+ {
+ BOOTPClient *bc;
+
+ bc = &bootp_clients[i];
+ bc->allocated = 1;
+ paddr->s_addr = RT_H2N_U32(RT_N2H_U32(pData->special_addr.s_addr) | (i + START_ADDR));
+ LogFlowFunc(("LEAVE: paddr:%RTnaipv4 bc:%d\n", paddr->s_addr, bc->number));
+ return bc;
+ }
+ }
+ LogFlowFunc(("LEAVE: NULL\n"));
+ return NULL;
+}
+
+static struct mbuf *dhcp_create_msg(PNATState pData, struct bootp_t *bp, struct mbuf *m, uint8_t type)
+{
+ struct bootp_t *rbp;
+ struct ethhdr *eh;
+ uint8_t *q;
+
+ eh = mtod(m, struct ethhdr *);
+ memcpy(eh->h_source, bp->bp_hwaddr, ETH_ALEN); /* XXX: if_encap just swap source with dest */
+
+ m->m_data += if_maxlinkhdr; /*reserve ether header */
+
+ rbp = mtod(m, struct bootp_t *);
+ memset(rbp, 0, sizeof(struct bootp_t));
+ rbp->bp_op = BOOTP_REPLY;
+ rbp->bp_xid = bp->bp_xid; /* see table 3 of rfc2131*/
+ rbp->bp_flags = bp->bp_flags; /* figure 2 of rfc2131 */
+ rbp->bp_giaddr.s_addr = bp->bp_giaddr.s_addr;
+#if 0 /*check flags*/
+ saddr.sin_port = RT_H2N_U16_C(BOOTP_SERVER);
+ daddr.sin_port = RT_H2N_U16_C(BOOTP_CLIENT);
+#endif
+ rbp->bp_htype = 1;
+ rbp->bp_hlen = 6;
+ memcpy(rbp->bp_hwaddr, bp->bp_hwaddr, 6);
+
+ memcpy(rbp->bp_vend, rfc1533_cookie, 4); /* cookie */
+ q = rbp->bp_vend;
+ q += 4;
+ *q++ = RFC2132_MSG_TYPE;
+ *q++ = 1;
+ *q++ = type;
+
+ return m;
+}
+
+static int dhcp_do_ack_offer(PNATState pData, struct mbuf *m, BOOTPClient *bc, int fDhcpRequest)
+{
+ struct bootp_t *rbp = NULL;
+ uint8_t *q;
+ struct in_addr saddr;
+ int val;
+
+ struct dns_entry *de = NULL;
+ struct dns_domain_entry *dd = NULL;
+ int added = 0;
+ uint8_t *q_dns_header = NULL;
+ uint32_t lease_time = RT_H2N_U32_C(LEASE_TIME);
+ uint32_t netmask = RT_H2N_U32(pData->netmask);
+
+ rbp = mtod(m, struct bootp_t *);
+ q = &rbp->bp_vend[0];
+ q += 7; /* !cookie rfc 2132 + TYPE*/
+
+ /*DHCP Offer specific*/
+ /*
+ * we're care in built-in tftp server about existence/validness of the boot file.
+ */
+ if (bootp_filename)
+ RTStrPrintf((char*)rbp->bp_file, sizeof(rbp->bp_file), "%s", bootp_filename);
+
+ Log(("NAT: DHCP: bp_file:%s\n", &rbp->bp_file));
+ /* Address/port of the DHCP server. */
+ rbp->bp_yiaddr = bc->addr; /* Client IP address */
+ Log(("NAT: DHCP: bp_yiaddr:%RTnaipv4\n", rbp->bp_yiaddr.s_addr));
+ rbp->bp_siaddr = pData->tftp_server; /* Next Server IP address, i.e. TFTP */
+ Log(("NAT: DHCP: bp_siaddr:%RTnaipv4\n", rbp->bp_siaddr.s_addr));
+ if (fDhcpRequest)
+ {
+ rbp->bp_ciaddr.s_addr = bc->addr.s_addr; /* Client IP address */
+ }
+ saddr.s_addr = RT_H2N_U32(RT_N2H_U32(pData->special_addr.s_addr) | CTL_ALIAS);
+ Log(("NAT: DHCP: s_addr:%RTnaipv4\n", saddr.s_addr));
+
+#define FILL_BOOTP_EXT(q, tag, len, pvalue) \
+ do { \
+ struct bootp_ext *be = (struct bootp_ext *)(q); \
+ be->bpe_tag = (tag); \
+ be->bpe_len = (len); \
+ memcpy(&be[1], (pvalue), (len)); \
+ (q) = (uint8_t *)(&be[1]) + (len); \
+ }while(0)
+/* appending another value to tag, calculates len of whole block*/
+#define FILL_BOOTP_APP(head, q, tag, len, pvalue) \
+ do { \
+ struct bootp_ext *be = (struct bootp_ext *)(head); \
+ memcpy(q, (pvalue), (len)); \
+ (q) += (len); \
+ Assert(be->bpe_tag == (tag)); \
+ be->bpe_len += (len); \
+ }while(0)
+
+
+ FILL_BOOTP_EXT(q, RFC1533_NETMASK, 4, &netmask);
+ FILL_BOOTP_EXT(q, RFC1533_GATEWAY, 4, &saddr);
+
+ if (pData->fUseDnsProxy || pData->fUseHostResolver)
+ {
+ uint32_t addr = RT_H2N_U32(RT_N2H_U32(pData->special_addr.s_addr) | CTL_DNS);
+ FILL_BOOTP_EXT(q, RFC1533_DNS, 4, &addr);
+ }
+ else if (!TAILQ_EMPTY(&pData->pDnsList))
+ {
+ de = TAILQ_LAST(&pData->pDnsList, dns_list_head);
+ q_dns_header = q;
+ FILL_BOOTP_EXT(q, RFC1533_DNS, 4, &de->de_addr.s_addr);
+
+ TAILQ_FOREACH_REVERSE(de, &pData->pDnsList, dns_list_head, de_list)
+ {
+ if (TAILQ_LAST(&pData->pDnsList, dns_list_head) == de)
+ continue; /* first value with head we've ingected before */
+ FILL_BOOTP_APP(q_dns_header, q, RFC1533_DNS, 4, &de->de_addr.s_addr);
+ }
+ }
+
+ if (pData->fPassDomain && !pData->fUseHostResolver)
+ {
+ LIST_FOREACH(dd, &pData->pDomainList, dd_list)
+ {
+
+ if (dd->dd_pszDomain == NULL)
+ continue;
+ /* never meet valid separator here in RFC1533*/
+ if (added != 0)
+ FILL_BOOTP_EXT(q, RFC1533_DOMAINNAME, 1, ",");
+ else
+ added = 1;
+ val = (int)strlen(dd->dd_pszDomain);
+ FILL_BOOTP_EXT(q, RFC1533_DOMAINNAME, val, dd->dd_pszDomain);
+ }
+ }
+
+ FILL_BOOTP_EXT(q, RFC2132_LEASE_TIME, 4, &lease_time);
+
+ if (*slirp_hostname)
+ {
+ val = (int)strlen(slirp_hostname);
+ FILL_BOOTP_EXT(q, RFC1533_HOSTNAME, val, slirp_hostname);
+ }
+ /* Temporary fix: do not pollute ARP cache from BOOTP because it may result
+ in network loss due to cache entry override w/ invalid MAC address. */
+ /*slirp_arp_cache_update_or_add(pData, rbp->bp_yiaddr.s_addr, bc->macaddr);*/
+ return q - rbp->bp_vend; /*return offset */
+}
+
+static int dhcp_send_nack(PNATState pData, struct bootp_t *bp, BOOTPClient *bc, struct mbuf *m)
+{
+ NOREF(bc);
+
+ dhcp_create_msg(pData, bp, m, DHCPNAK);
+ return 7;
+}
+
+static int dhcp_send_ack(PNATState pData, struct bootp_t *bp, BOOTPClient *bc, struct mbuf *m, int fDhcpRequest)
+{
+ int offReply = 0; /* boot_reply will fill general options and add END before sending response */
+
+ AssertReturn(bc != NULL, -1);
+
+ dhcp_create_msg(pData, bp, m, DHCPACK);
+ slirp_update_guest_addr_guess(pData, bc->addr.s_addr, "DHCP ACK");
+ offReply = dhcp_do_ack_offer(pData, m, bc, fDhcpRequest);
+ return offReply;
+}
+
+static int dhcp_send_offer(PNATState pData, struct bootp_t *bp, BOOTPClient *bc, struct mbuf *m)
+{
+ int offReply = 0; /* boot_reply will fill general options and add END before sending response */
+
+ dhcp_create_msg(pData, bp, m, DHCPOFFER);
+ offReply = dhcp_do_ack_offer(pData, m, bc, /* fDhcpRequest=*/ 0);
+ return offReply;
+}
+
+/**
+ * decoding client messages RFC2131 (4.3.6)
+ * ---------------------------------------------------------------------
+ * | |INIT-REBOOT |SELECTING |RENEWING |REBINDING |
+ * ---------------------------------------------------------------------
+ * |broad/unicast |broadcast |broadcast |unicast |broadcast |
+ * |server-ip |MUST NOT |MUST |MUST NOT |MUST NOT |
+ * |requested-ip |MUST |MUST |MUST NOT |MUST NOT |
+ * |ciaddr |zero |zero |IP address |IP address|
+ * ---------------------------------------------------------------------
+ *
+ */
+
+enum DHCP_REQUEST_STATES
+{
+ INIT_REBOOT,
+ SELECTING,
+ RENEWING,
+ REBINDING,
+ NONE
+};
+
+static int dhcp_decode_request(PNATState pData, struct bootp_t *bp, size_t vlen, struct mbuf *m)
+{
+ BOOTPClient *bc = NULL;
+ struct in_addr daddr;
+ int offReply;
+ uint8_t *req_ip = NULL;
+ uint8_t *server_ip = NULL;
+ uint32_t ui32;
+ enum DHCP_REQUEST_STATES dhcp_stat = NONE;
+
+ /* need to understand which type of request we get */
+ req_ip = dhcp_find_option(bp->bp_vend, vlen,
+ RFC2132_REQ_ADDR, sizeof(struct in_addr));
+ server_ip = dhcp_find_option(bp->bp_vend, vlen,
+ RFC2132_SRV_ID, sizeof(struct in_addr));
+
+ bc = find_addr(pData, &daddr, bp->bp_hwaddr);
+
+ if (server_ip != NULL)
+ {
+ /* selecting */
+ if (!bc)
+ {
+ LogRel(("NAT: DHCP no IP was allocated\n"));
+ return -1;
+ }
+
+ if ( !req_ip
+ || bp->bp_ciaddr.s_addr != INADDR_ANY)
+ {
+ LogRel(("NAT: Invalid SELECTING request\n"));
+ return -1; /* silently ignored */
+ }
+ dhcp_stat = SELECTING;
+ /* Assert((bp->bp_ciaddr.s_addr == INADDR_ANY)); */
+ }
+ else
+ {
+ if (req_ip != NULL)
+ {
+ /* init-reboot */
+ dhcp_stat = INIT_REBOOT;
+ }
+ else
+ {
+ /* table 4 of rfc2131 */
+ if (bp->bp_flags & RT_H2N_U16_C(DHCP_FLAGS_B))
+ dhcp_stat = REBINDING;
+ else
+ dhcp_stat = RENEWING;
+ }
+ }
+
+ /*?? renewing ??*/
+ switch (dhcp_stat)
+ {
+ case RENEWING:
+ /**
+ * decoding client messages RFC2131 (4.3.6)
+ * ------------------------------
+ * | |RENEWING |
+ * ------------------------------
+ * |broad/unicast |unicast |
+ * |server-ip |MUST NOT |
+ * |requested-ip |MUST NOT |
+ * |ciaddr |IP address |
+ * ------------------------------
+ */
+ if ( server_ip
+ || req_ip
+ || bp->bp_ciaddr.s_addr == INADDR_ANY)
+ {
+ LogRel(("NAT: Invalid RENEWING dhcp request\n"));
+ return -1; /* silent ignorance */
+ }
+ if (bc != NULL)
+ {
+ /* Assert((bc->addr.s_addr == bp->bp_ciaddr.s_addr)); */
+ /*if it already here well just do ack, we aren't aware of dhcp time expiration*/
+ }
+ else
+ {
+ if ((bp->bp_ciaddr.s_addr & RT_H2N_U32(pData->netmask)) != pData->special_addr.s_addr)
+ {
+ LogRel(("NAT: Client %RTnaipv4 requested IP -- sending NAK\n", bp->bp_ciaddr.s_addr));
+ offReply = dhcp_send_nack(pData, bp, bc, m);
+ return offReply;
+ }
+
+ bc = bc_alloc_client(pData);
+ if (!bc)
+ {
+ LogRel(("NAT: Can't allocate address. RENEW has been silently ignored\n"));
+ return -1;
+ }
+
+ memcpy(bc->macaddr, bp->bp_hwaddr, ETH_ALEN);
+ bc->addr.s_addr = bp->bp_ciaddr.s_addr;
+ }
+ break;
+
+ case INIT_REBOOT:
+ /**
+ * decoding client messages RFC2131 (4.3.6)
+ * ------------------------------
+ * | |INIT-REBOOT |
+ * ------------------------------
+ * |broad/unicast |broadcast |
+ * |server-ip |MUST NOT |
+ * |requested-ip |MUST |
+ * |ciaddr |zero |
+ * ------------------------------
+ *
+ */
+ if ( server_ip
+ || !req_ip
+ || bp->bp_ciaddr.s_addr != INADDR_ANY)
+ {
+ LogRel(("NAT: Invalid INIT-REBOOT dhcp request\n"));
+ return -1; /* silently ignored */
+ }
+ ui32 = *(uint32_t *)(req_ip + 2);
+ if ((ui32 & RT_H2N_U32(pData->netmask)) != pData->special_addr.s_addr)
+ {
+ LogRel(("NAT: Address %RTnaipv4 has been requested -- sending NAK\n", ui32));
+ offReply = dhcp_send_nack(pData, bp, bc, m);
+ return offReply;
+ }
+
+ /* find_addr() got some result? */
+ if (!bc)
+ {
+ bc = bc_alloc_client(pData);
+ if (!bc)
+ {
+ LogRel(("NAT: Can't allocate address. RENEW has been silently ignored\n"));
+ return -1;
+ }
+ }
+
+ memcpy(bc->macaddr, bp->bp_hwaddr, ETH_ALEN);
+ bc->addr.s_addr = ui32;
+ break;
+
+ case NONE:
+ return -1;
+
+ default:
+ break;
+ }
+
+ if (bc == NULL)
+ return -1;
+
+ LogRel(("NAT: DHCP offered IP address %RTnaipv4\n", bc->addr.s_addr));
+ offReply = dhcp_send_ack(pData, bp, bc, m, /* fDhcpRequest=*/ 1);
+ return offReply;
+}
+
+static int dhcp_decode_discover(PNATState pData, struct bootp_t *bp, int fDhcpDiscover, struct mbuf *m)
+{
+ BOOTPClient *bc;
+ struct in_addr daddr;
+ int offReply;
+
+ if (fDhcpDiscover)
+ {
+ bc = find_addr(pData, &daddr, bp->bp_hwaddr);
+ if (!bc)
+ {
+ bc = get_new_addr(pData, &daddr);
+ if (!bc)
+ {
+ LogRel(("NAT: DHCP no IP address left\n"));
+ Log(("no address left\n"));
+ return -1;
+ }
+ memcpy(bc->macaddr, bp->bp_hwaddr, ETH_ALEN);
+ }
+
+ bc->xid = bp->bp_xid;
+ LogRel(("NAT: DHCP offered IP address %RTnaipv4\n", bc->addr.s_addr));
+ offReply = dhcp_send_offer(pData, bp, bc, m);
+ return offReply;
+ }
+
+ bc = find_addr(pData, &daddr, bp->bp_hwaddr);
+ if (!bc)
+ {
+ LogRel(("NAT: DHCP Inform was ignored no boot client was found\n"));
+ return -1;
+ }
+
+ LogRel(("NAT: DHCP offered IP address %RTnaipv4\n", bc->addr.s_addr));
+ offReply = dhcp_send_ack(pData, bp, bc, m, /* fDhcpRequest=*/ 0);
+ return offReply;
+}
+
+static int dhcp_decode_release(PNATState pData, struct bootp_t *bp)
+{
+ int rc = release_addr(pData, &bp->bp_ciaddr);
+ LogRel(("NAT: %s %RTnaipv4\n",
+ RT_SUCCESS(rc) ? "DHCP released IP address" : "Ignored DHCP release for IP address",
+ bp->bp_ciaddr.s_addr));
+ return 0;
+}
+
+/**
+ * fields for discovering t
+ * Field DHCPDISCOVER DHCPREQUEST DHCPDECLINE,
+ * DHCPINFORM DHCPRELEASE
+ * ----- ------------ ----------- -----------
+ * 'op' BOOTREQUEST BOOTREQUEST BOOTREQUEST
+ * 'htype' (From "Assigned Numbers" RFC)
+ * 'hlen' (Hardware address length in octets)
+ * 'hops' 0 0 0
+ * 'xid' selected by client 'xid' from server selected by
+ * DHCPOFFER message client
+ * 'secs' 0 or seconds since 0 or seconds since 0
+ * DHCP process started DHCP process started
+ * 'flags' Set 'BROADCAST' Set 'BROADCAST' 0
+ * flag if client flag if client
+ * requires broadcast requires broadcast
+ * reply reply
+ * 'ciaddr' 0 (DHCPDISCOVER) 0 or client's 0 (DHCPDECLINE)
+ * client's network address client's network
+ * network address (BOUND/RENEW/REBIND) address
+ * (DHCPINFORM) (DHCPRELEASE)
+ * 'yiaddr' 0 0 0
+ * 'siaddr' 0 0 0
+ * 'giaddr' 0 0 0
+ * 'chaddr' client's hardware client's hardware client's hardware
+ * address address address
+ * 'sname' options, if options, if (unused)
+ * indicated in indicated in
+ * 'sname/file' 'sname/file'
+ * option; otherwise option; otherwise
+ * unused unused
+ * 'file' options, if options, if (unused)
+ * indicated in indicated in
+ * 'sname/file' 'sname/file'
+ * option; otherwise option; otherwise
+ * unused unused
+ * 'options' options options (unused)
+ * Requested IP address MAY MUST (in MUST
+ * (DISCOVER) SELECTING or (DHCPDECLINE),
+ * MUST NOT INIT-REBOOT) MUST NOT
+ * (INFORM) MUST NOT (in (DHCPRELEASE)
+ * BOUND or
+ * RENEWING)
+ * IP address lease time MAY MAY MUST NOT
+ * (DISCOVER)
+ * MUST NOT
+ * (INFORM)
+ * Use 'file'/'sname' fields MAY MAY MAY
+ * DHCP message type DHCPDISCOVER/ DHCPREQUEST DHCPDECLINE/
+ * DHCPINFORM DHCPRELEASE
+ * Client identifier MAY MAY MAY
+ * Vendor class identifier MAY MAY MUST NOT
+ * Server identifier MUST NOT MUST (after MUST
+ * SELECTING)
+ * MUST NOT (after
+ * INIT-REBOOT,
+ * BOUND, RENEWING
+ * or REBINDING)
+ * Parameter request list MAY MAY MUST NOT
+ * Maximum message size MAY MAY MUST NOT
+ * Message SHOULD NOT SHOULD NOT SHOULD
+ * Site-specific MAY MAY MUST NOT
+ * All others MAY MAY MUST NOT
+ *
+ */
+static void dhcp_decode(PNATState pData, struct bootp_t *bp, size_t vlen)
+{
+ const uint8_t *pu8RawDhcpObject;
+ int rc;
+ struct in_addr req_ip;
+ int fDhcpDiscover = 0;
+ uint8_t *parameter_list = NULL;
+ struct mbuf *m = NULL;
+
+ if (memcmp(bp->bp_vend, rfc1533_cookie, sizeof(rfc1533_cookie)) != 0)
+ return;
+
+ pu8RawDhcpObject = dhcp_find_option(bp->bp_vend, vlen, RFC2132_MSG_TYPE, 1);
+ if (pu8RawDhcpObject == NULL)
+ return;
+ if (pu8RawDhcpObject[1] != 1) /* option length */
+ return;
+
+ /**
+ * We're going update dns list at least once per DHCP transaction (!not on every operation
+ * within transaction), assuming that transaction can't be longer than 1 min.
+ *
+ * @note: if we have notification update (HAVE_NOTIFICATION_FOR_DNS_UPDATE)
+ * provided by host, we don't need implicitly re-initialize dns list.
+ *
+ * @note: NATState::fUseHostResolver became (r89055) the flag signalling that Slirp
+ * wasn't able to fetch fresh host DNS info and fall down to use host-resolver, on one
+ * of the previous attempts to proxy dns requests to Host's name-resolving API
+ *
+ * @note: Checking NATState::fUseHostResolver == true, we want to try restore behaviour initialy
+ * wanted by user ASAP (P here when host serialize its configuration in files parsed by Slirp).
+ */
+ if ( !HAVE_NOTIFICATION_FOR_DNS_UPDATE
+ && !pData->fUseHostResolverPermanent
+ && ( pData->dnsLastUpdate == 0
+ || curtime - pData->dnsLastUpdate > 60 * 1000 /* one minute */
+ || pData->fUseHostResolver))
+ {
+ uint8_t i;
+
+ parameter_list = dhcp_find_option(bp->bp_vend, vlen, RFC2132_PARAM_LIST, -1);
+ for (i = 0; parameter_list && i < parameter_list[1]; ++i)
+ {
+ if (parameter_list[2 + i] == RFC1533_DNS)
+ {
+ /* XXX: How differs it from host Suspend/Resume? */
+ slirpReleaseDnsSettings(pData);
+ slirpInitializeDnsSettings(pData);
+ pData->dnsLastUpdate = curtime;
+ break;
+ }
+ }
+ }
+
+ m = m_getcl(pData, M_DONTWAIT, MT_HEADER, M_PKTHDR);
+ if (!m)
+ {
+ LogRel(("NAT: Can't allocate memory for response!\n"));
+ return;
+ }
+
+ switch (*(pu8RawDhcpObject + 2))
+ {
+ case DHCPDISCOVER:
+ fDhcpDiscover = 1;
+ RT_FALL_THRU();
+ case DHCPINFORM:
+ rc = dhcp_decode_discover(pData, bp, fDhcpDiscover, m);
+ if (rc > 0)
+ goto reply;
+ break;
+
+ case DHCPREQUEST:
+ rc = dhcp_decode_request(pData, bp, vlen, m);
+ if (rc > 0)
+ goto reply;
+ break;
+
+ case DHCPRELEASE:
+ dhcp_decode_release(pData, bp);
+ /* no reply required */
+ break;
+
+ case DHCPDECLINE:
+ pu8RawDhcpObject = dhcp_find_option(bp->bp_vend, vlen,
+ RFC2132_REQ_ADDR, sizeof(struct in_addr));
+ if (pu8RawDhcpObject == NULL)
+ {
+ Log(("NAT: RFC2132_REQ_ADDR not found\n"));
+ break;
+ }
+
+ req_ip.s_addr = *(uint32_t *)(pu8RawDhcpObject + 2);
+ rc = bootp_cache_lookup_ether_by_ip(pData, req_ip.s_addr, NULL);
+ if (RT_FAILURE(rc))
+ {
+ /* Not registered */
+ BOOTPClient *bc;
+ bc = bc_alloc_client(pData);
+ Assert(bc);
+ if (!bc)
+ {
+ LogRel(("NAT: Can't allocate bootp client object\n"));
+ break;
+ }
+ bc->addr.s_addr = req_ip.s_addr;
+ slirp_arp_who_has(pData, bc->addr.s_addr);
+ LogRel(("NAT: %RTnaipv4 has been already registered\n", req_ip));
+ }
+ /* no response required */
+ break;
+
+ default:
+ /* unsupported DHCP message type */
+ break;
+ }
+ /* silently ignore */
+ m_freem(pData, m);
+ return;
+
+reply:
+ bootp_reply(pData, m, rc, bp->bp_flags);
+}
+
+static void bootp_reply(PNATState pData, struct mbuf *m, int offReply, uint16_t flags)
+{
+ struct sockaddr_in saddr, daddr;
+ struct bootp_t *rbp = NULL;
+ uint8_t *q = NULL;
+ int nack;
+ rbp = mtod(m, struct bootp_t *);
+ Assert((m));
+ Assert((rbp));
+ q = rbp->bp_vend;
+ nack = (q[6] == DHCPNAK);
+ q += offReply;
+
+ saddr.sin_addr.s_addr = RT_H2N_U32(RT_N2H_U32(pData->special_addr.s_addr) | CTL_ALIAS);
+
+ FILL_BOOTP_EXT(q, RFC2132_SRV_ID, 4, &saddr.sin_addr);
+
+ *q++ = RFC1533_END; /* end of message */
+
+ m->m_pkthdr.header = mtod(m, void *);
+ m->m_len = sizeof(struct bootp_t)
+ - sizeof(struct ip)
+ - sizeof(struct udphdr);
+ m->m_data += sizeof(struct udphdr)
+ + sizeof(struct ip);
+ if ( (flags & RT_H2N_U16_C(DHCP_FLAGS_B))
+ || nack != 0)
+ daddr.sin_addr.s_addr = INADDR_BROADCAST;
+ else
+ daddr.sin_addr.s_addr = rbp->bp_yiaddr.s_addr; /*unicast requested by client*/
+ saddr.sin_port = RT_H2N_U16_C(BOOTP_SERVER);
+ daddr.sin_port = RT_H2N_U16_C(BOOTP_CLIENT);
+ udp_output2(pData, NULL, m, &saddr, &daddr, IPTOS_LOWDELAY);
+}
+
+void bootp_input(PNATState pData, struct mbuf *m)
+{
+ struct bootp_t *bp = mtod(m, struct bootp_t *);
+ u_int mlen = m_length(m, NULL);
+ size_t vlen;
+
+ if (mlen < RT_UOFFSETOF(struct bootp_t, bp_vend) + sizeof(rfc1533_cookie))
+ {
+ LogRelMax(50, ("NAT: ignoring invalid BOOTP request (mlen %u too short)\n", mlen));
+ return;
+ }
+
+ if (bp->bp_op != BOOTP_REQUEST)
+ {
+ LogRelMax(50, ("NAT: ignoring invalid BOOTP request (wrong opcode %u)\n", bp->bp_op));
+ return;
+ }
+
+ if (bp->bp_htype != RTNET_ARP_ETHER)
+ {
+ LogRelMax(50, ("NAT: ignoring invalid BOOTP request (wrong HW type %u)\n", bp->bp_htype));
+ return;
+ }
+
+ if (bp->bp_hlen != ETH_ALEN)
+ {
+ LogRelMax(50, ("NAT: ignoring invalid BOOTP request (wrong HW address length %u)\n", bp->bp_hlen));
+ return;
+ }
+
+ if (bp->bp_hops != 0)
+ {
+ LogRelMax(50, ("NAT: ignoring invalid BOOTP request (wrong hop count %u)\n", bp->bp_hops));
+ return;
+ }
+
+ vlen = mlen - RT_UOFFSETOF(struct bootp_t, bp_vend);
+ dhcp_decode(pData, bp, vlen);
+}
+
+int bootp_cache_lookup_ip_by_ether(PNATState pData,const uint8_t* ether, uint32_t *pip)
+{
+ int i;
+
+ if (!ether || !pip)
+ return VERR_INVALID_PARAMETER;
+
+ for (i = 0; i < NB_ADDR; i++)
+ {
+ if ( bootp_clients[i].allocated
+ && memcmp(bootp_clients[i].macaddr, ether, ETH_ALEN) == 0)
+ {
+ *pip = bootp_clients[i].addr.s_addr;
+ return VINF_SUCCESS;
+ }
+ }
+
+ *pip = INADDR_ANY;
+ return VERR_NOT_FOUND;
+}
+
+int bootp_cache_lookup_ether_by_ip(PNATState pData, uint32_t ip, uint8_t *ether)
+{
+ int i;
+ for (i = 0; i < NB_ADDR; i++)
+ {
+ if ( bootp_clients[i].allocated
+ && ip == bootp_clients[i].addr.s_addr)
+ {
+ if (ether != NULL)
+ memcpy(ether, bootp_clients[i].macaddr, ETH_ALEN);
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+/*
+ * Initialize dhcp server
+ * @returns 0 - if initialization is ok, non-zero otherwise
+ */
+int bootp_dhcp_init(PNATState pData)
+{
+ pData->pbootp_clients = RTMemAllocZ(sizeof(BOOTPClient) * NB_ADDR);
+ if (!pData->pbootp_clients)
+ return VERR_NO_MEMORY;
+
+ return VINF_SUCCESS;
+}
+
+int bootp_dhcp_fini(PNATState pData)
+{
+ if (pData->pbootp_clients != NULL)
+ RTMemFree(pData->pbootp_clients);
+
+ return VINF_SUCCESS;
+}