// SPDX-License-Identifier: AGPL-3.0-or-later /* PASST - Plug A Simple Socket Transport * * dhcp.c - Minimalistic DHCP server for PASST * * Copyright (c) 2020-2021 Red Hat GmbH * Author: Stefano Brivio * */ #include #include #include #include #include #include #include #include #include #include #include #include "passt.h" #include "dhcp.h" #include "util.h" #include "tap.h" /** * struct opt - DHCP option * @sent: Convenience flag, set while filling replies * @slen: Length of option defined for server * @s: Option payload from server * @clen: Length of option received from client * @c: Option payload from client */ struct opt { int sent; int slen; unsigned char s[255]; int clen; unsigned char c[255]; }; static struct opt opts[255] = { [1] = { 0, 4, { 0 }, 0, { 0 }, }, /* Subnet mask */ [3] = { 0, 4, { 0 }, 0, { 0 }, }, /* Router */ [6] = { 0, 4, { 0 }, 0, { 0 }, }, /* DNS */ [51] = { 0, 4, { 60 }, 0, { 0 }, }, /* Lease time */ [53] = { 0, 1, { 0 }, 0, { 0 }, }, /* Message type */ #define DHCPDISCOVER 1 #define DHCPOFFER 2 #define DHCPREQUEST 3 #define DHCPDECLINE 4 #define DHCPACK 5 #define DHCPNAK 6 #define DHCPRELEASE 7 #define DHCPINFORM 8 #define DHCPFORCERENEW 9 [54] = { 0, 4, { 0 }, 0, { 0 }, }, /* Server ID */ }; /** * struct msg - BOOTP/DHCP message * @op: BOOTP message type * @htype: Hardware address type * @hlen: Hardware address length * @hops: DHCP relay hops * @xid: Transaction ID randomly chosen by client * @secs: Seconds elapsed since beginning of acquisition or renewal * @flags: DHCP message flags * @ciaddr: Client IP address in BOUND, RENEW, REBINDING * @yiaddr: IP address being offered or assigned * @siaddr: Next server to use in bootstrap * @giaddr: Relay agent IP address * @chaddr: Client hardware address * @sname: Server host name * @file: Boot file name * @magic: Magic cookie prefix before options * @o: Options */ struct msg { uint8_t op; #define BOOTREQUEST 1 #define BOOTREPLY 2 uint8_t htype; uint8_t hlen; uint8_t hops; uint32_t xid; uint16_t secs; uint16_t flags; uint32_t ciaddr; uint32_t yiaddr; uint32_t siaddr; uint32_t giaddr; uint8_t chaddr[16]; uint8_t sname[64]; uint8_t file[128]; uint32_t magic; uint8_t o[308]; } __attribute__((__packed__)); /** * fill_one() - Fill a single option in message * @m: Message to fill * @o: Option number * @offset: Current offset within options field, updated on insertion */ static void fill_one(struct msg *m, int o, int *offset) { m->o[*offset] = o; m->o[*offset + 1] = opts[o].slen; memcpy(&m->o[*offset + 2], opts[o].s, opts[o].slen); opts[o].sent = 1; *offset += 2 + opts[o].slen; } /** * fill() - Fill options in message * @m: Message to fill * * Return: current size of options field */ static int fill(struct msg *m) { int i, o, offset = 0; m->op = BOOTREPLY; m->secs = 0; for (o = 0; o < 255; o++) opts[o].sent = 0; for (i = 0; i < opts[55].clen; i++) { o = opts[55].c[i]; if (opts[o].slen) fill_one(m, o, &offset); } for (o = 0; o < 255; o++) { if (opts[o].slen && !opts[o].sent) fill_one(m, o, &offset); } m->o[offset++] = 255; m->o[offset++] = 0; if (offset < 62 /* RFC 951 */) { memset(&m->o[offset], 0, 62 - offset); offset = 62; } return offset; } /** * dhcp() - Check if this is a DHCP message, reply as needed * @c: Execution context * @len: Total L2 packet length * @eh: Packet buffer, Ethernet header * * Return: 0 if it's not a DHCP message, 1 if handled, -1 on failure */ int dhcp(struct ctx *c, struct ethhdr *eh, size_t len) { struct iphdr *iph = (struct iphdr *)(eh + 1); size_t mlen, olen; struct udphdr *uh; unsigned int i; struct msg *m; if (len < sizeof(*eh) + sizeof(*iph)) return 0; if (len < sizeof(*eh) + iph->ihl * 4 + sizeof(*uh)) return 0; uh = (struct udphdr *)((char *)iph + iph->ihl * 4); m = (struct msg *)(uh + 1); if (uh->dest != htons(67)) return 0; mlen = len - sizeof(*eh) - iph->ihl * 4 - sizeof(*uh); if (mlen != ntohs(uh->len) - sizeof(*uh) || mlen < offsetof(struct msg, o) || m->op != BOOTREQUEST) return -1; olen = mlen - offsetof(struct msg, o); for (i = 0; i + 2 < olen; i += m->o[i + 1] + 2) { if (m->o[i + 1] + i + 2 >= olen) return -1; memcpy(&opts[m->o[i]].c, &m->o[i + 2], m->o[i + 1]); } if (opts[53].c[0] == DHCPDISCOVER) { info("DHCP: offer to discover"); opts[53].s[0] = DHCPOFFER; } else if (opts[53].c[0] == DHCPREQUEST) { info("DHCP: ack to request"); opts[53].s[0] = DHCPACK; } else { return -1; } info(" from %02x:%02x:%02x:%02x:%02x:%02x", m->chaddr[0], m->chaddr[1], m->chaddr[2], m->chaddr[3], m->chaddr[4], m->chaddr[5]); m->yiaddr = c->addr4; *(unsigned long *)opts[1].s = c->mask4; *(unsigned long *)opts[3].s = c->gw4; *(unsigned long *)opts[6].s = c->dns4; *(unsigned long *)opts[54].s = c->gw4; uh->len = htons(len = offsetof(struct msg, o) + fill(m)); uh->check = 0; uh->source = htons(67); uh->dest = htons(68); iph->tot_len = htons(len += sizeof(*iph)); iph->daddr = c->addr4; iph->saddr = c->gw4; iph->check = 0; iph->check = csum_ip4(iph, iph->ihl * 4); len += sizeof(*eh); memcpy(eh->h_dest, eh->h_source, ETH_ALEN); memcpy(eh->h_source, c->mac, ETH_ALEN); if (tap_send(c->fd_unix, eh, len, 0) < 0) perror("DHCP: send"); return 1; }