diff options
author | Stefano Brivio <sbrivio@redhat.com> | 2020-07-20 16:27:43 +0200 |
---|---|---|
committer | Stefano Brivio <sbrivio@redhat.com> | 2021-02-16 07:57:57 +0100 |
commit | b439984641edaf4e781dc424d4c8a574461d3540 (patch) | |
tree | ecdd49d889bc5e566e59c390e58ade3804d0a7f4 /dhcp.c | |
parent | fa2d20908d061fc7a4c56e793487da861af58aca (diff) | |
download | passt-b439984641edaf4e781dc424d4c8a574461d3540.tar passt-b439984641edaf4e781dc424d4c8a574461d3540.tar.gz passt-b439984641edaf4e781dc424d4c8a574461d3540.tar.bz2 passt-b439984641edaf4e781dc424d4c8a574461d3540.tar.lz passt-b439984641edaf4e781dc424d4c8a574461d3540.tar.xz passt-b439984641edaf4e781dc424d4c8a574461d3540.tar.zst passt-b439984641edaf4e781dc424d4c8a574461d3540.zip |
merd: ARP and DHCP handlers, connection tracking fixes
With this, merd provides a fully functional IPv4 environment to
guests, requiring a single capability, CAP_NET_RAW.
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
Diffstat (limited to 'dhcp.c')
-rw-r--r-- | dhcp.c | 218 |
1 files changed, 218 insertions, 0 deletions
@@ -0,0 +1,218 @@ +/* MERD - MacVTap Egress and Routing Daemon + * + * dhcp.c - Minimalistic DHCP server for MERD + * + * Author: Stefano Brivio <sbrivio@redhat.com> + * License: GPLv2 + * + */ + +#include <stdio.h> +#include <stddef.h> +#include <stdint.h> +#include <unistd.h> +#include <string.h> +#include <linux/if_ether.h> +#include <linux/ip.h> +#include <linux/udp.h> +#include <net/if.h> +#include <arpa/inet.h> + +#include "merd.h" +#include "dhcp.h" +#include "util.h" + +/** + * struct opt - DHCP option + * @force: Force sending, even if the client didn't request it + * @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 requested and forced 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, unsigned len, struct ethhdr *eh) +{ + struct iphdr *iph = (struct iphdr *)(eh + 1); + struct udphdr *uh = (struct udphdr *)((char *)iph + iph->ihl * 4); + struct msg *m = (struct msg *)(uh + 1); + unsigned int i, mlen = len - sizeof(*eh) - sizeof(*iph); + + if (uh->dest != htons(67)) + return 0; + + if (mlen != ntohs(uh->len) || mlen < offsetof(struct msg, o) || + m->op != BOOTREQUEST) + return -1; + + for (i = 0; i < mlen - offsetof(struct msg, o); i += m->o[i + 1] + 2) + memcpy(&opts[m->o[i]].c, &m->o[i + 2], m->o[i + 1]); + + if (opts[53].c[0] == DHCPDISCOVER) { + fprintf(stderr, "DHCP: offer to discover"); + opts[53].s[0] = DHCPOFFER; + } else if (opts[53].c[0] == DHCPREQUEST) { + fprintf(stderr, "DHCP: ack to request"); + opts[53].s[0] = DHCPACK; + } else { + return -1; + } + + fprintf(stderr, " from %02x:%02x:%02x:%02x:%02x:%02x\n\n", + 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 (send(c->fd_unix, eh, len, 0) < 0) + perror("DHCP: send"); + + return 1; +} |